コード例 #1
0
bool GrStrokePathRenderer::canDrawPath(const SkPath& path,
                                       const SkStrokeRec& stroke,
                                       const GrDrawTarget* target,
                                       bool antiAlias) const {
    // FIXME : put the proper condition once GrDrawTarget::isOpaque is implemented
    const bool isOpaque = true; // target->isOpaque();

    // FIXME : remove this requirement once we have AA circles and implement the
    //         circle joins/caps appropriately in the ::onDrawPath() function.
    const bool requiresAACircle = (stroke.getCap()  == SkPaint::kRound_Cap) ||
                                  (stroke.getJoin() == SkPaint::kRound_Join);

    // Indices being stored in uint16, we don't want to overflow the indices capacity
    static const int maxVBSize = 1 << 16;
    const int maxNbVerts = (path.countPoints() + 1) * 5;

    // Check that the path contains no curved lines, only straight lines
    static const uint32_t unsupportedMask = SkPath::kQuad_SegmentMask | SkPath::kCubic_SegmentMask;

    // Must not be filled nor hairline nor semi-transparent
    // Note : May require a check to path.isConvex() if AA is supported
    return ((stroke.getStyle() == SkStrokeRec::kStroke_Style) && (maxNbVerts < maxVBSize) &&
            !path.isInverseFillType() && isOpaque && !requiresAACircle && !antiAlias &&
            ((path.getSegmentMasks() & unsupportedMask) == 0));
}
SkPathStroker::SkPathStroker(const SkPath& src,
                             SkScalar radius, SkScalar miterLimit,
                             SkPaint::Cap cap, SkPaint::Join join)
        : fRadius(radius) {

    /*  This is only used when join is miter_join, but we initialize it here
        so that it is always defined, to fis valgrind warnings.
    */
    fInvMiterLimit = 0;

    if (join == SkPaint::kMiter_Join) {
        if (miterLimit <= SK_Scalar1) {
            join = SkPaint::kBevel_Join;
        } else {
            fInvMiterLimit = SkScalarInvert(miterLimit);
        }
    }
    fCapper = SkStrokerPriv::CapFactory(cap);
    fJoiner = SkStrokerPriv::JoinFactory(join);
    fSegmentCount = -1;
    fPrevIsLine = false;

    // Need some estimate of how large our final result (fOuter)
    // and our per-contour temp (fInner) will be, so we don't spend
    // extra time repeatedly growing these arrays.
    //
    // 3x for result == inner + outer + join (swag)
    // 1x for inner == 'wag' (worst contour length would be better guess)
    fOuter.incReserve(src.countPoints() * 3);
    fInner.incReserve(src.countPoints());
}
コード例 #3
0
ファイル: GrShape.cpp プロジェクト: molikto/Skia
// Writes the path data key into the passed pointer.
static void write_path_key_from_data(const SkPath& path, uint32_t* origKey) {
    uint32_t* key = origKey;
    // The check below should take care of negative values casted positive.
    const int verbCnt = path.countVerbs();
    const int pointCnt = path.countPoints();
    const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path);
    SkASSERT(verbCnt <= GrShape::kMaxKeyFromDataVerbCnt);
    SkASSERT(pointCnt && verbCnt);
    *key++ = path.getFillType();
    *key++ = verbCnt;
    memcpy(key, SkPathPriv::VerbData(path), verbCnt * sizeof(uint8_t));
    int verbKeySize = SkAlign4(verbCnt);
    // pad out to uint32_t alignment using value that will stand out when debugging.
    uint8_t* pad = reinterpret_cast<uint8_t*>(key)+ verbCnt;
    memset(pad, 0xDE, verbKeySize - verbCnt);
    key += verbKeySize >> 2;

    memcpy(key, SkPathPriv::PointData(path), sizeof(SkPoint) * pointCnt);
    GR_STATIC_ASSERT(sizeof(SkPoint) == 2 * sizeof(uint32_t));
    key += 2 * pointCnt;
    sk_careful_memcpy(key, SkPathPriv::ConicWeightData(path), sizeof(SkScalar) * conicWeightCnt);
    GR_STATIC_ASSERT(sizeof(SkScalar) == sizeof(uint32_t));
    SkDEBUGCODE(key += conicWeightCnt);
    SkASSERT(key - origKey == path_key_from_data_size(path));
}
GrGLPath::GrGLPath(GrGpuGL* gpu, const SkPath& path) : INHERITED(gpu, kIsWrapped) {
#ifndef SK_SCALAR_IS_FLOAT
    GrCrash("Assumes scalar is float.");
#endif
    SkASSERT(!path.isEmpty());

    GL_CALL_RET(fPathID, GenPaths(1));

    SkSTArray<16, GrGLubyte, true> pathCommands;
    SkSTArray<16, SkPoint, true> pathPoints;

    int verbCnt = path.countVerbs();
    int pointCnt = path.countPoints();
    pathCommands.resize_back(verbCnt);
    pathPoints.resize_back(pointCnt);

    // TODO: Direct access to path points since we could pass them on directly.
    path.getPoints(&pathPoints[0], pointCnt);
    path.getVerbs(&pathCommands[0], verbCnt);

    GR_DEBUGCODE(int numPts = 0);
    for (int i = 0; i < verbCnt; ++i) {
        SkPath::Verb v = static_cast<SkPath::Verb>(pathCommands[i]);
        pathCommands[i] = verb_to_gl_path_cmd(v);
        GR_DEBUGCODE(numPts += num_pts(v));
    }
    GrAssert(pathPoints.count() == numPts);

    GL_CALL(PathCommands(fPathID,
                         verbCnt, &pathCommands[0],
                         2 * pointCnt, GR_GL_FLOAT, &pathPoints[0]));
    fBounds = path.getBounds();
}
コード例 #5
0
static HB_Error getOutlinePoint(HB_Font hbFont, HB_Glyph glyph, int flags,
                                hb_uint32 index, HB_Fixed* xPos, HB_Fixed* yPos,
                                hb_uint32* resultingNumPoints) {
    SkHarfBuzzFont* font = reinterpret_cast<SkHarfBuzzFont*>(hbFont->userData);
    SkPaint paint;

    font->setupPaint(&paint);
    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
    if (flags & HB_ShaperFlag_UseDesignMetrics) {
        paint.setHinting(SkPaint::kNo_Hinting);
    }

    SkPath path;
    uint16_t glyph16 = SkToU16(glyph);
    paint.getTextPath(&glyph16, sizeof(glyph16), 0, 0, &path);
    int numPoints = path.countPoints();
    if (index >= numPoints) {
        return HB_Err_Invalid_SubTable;
    }

    SkPoint pt = path.getPoint(index);
    *xPos = SkScalarToHarfbuzzFixed(pt.fX);
    *yPos = SkScalarToHarfbuzzFixed(pt.fY);
    *resultingNumPoints = numPoints;

    return HB_Err_Ok;
}
コード例 #6
0
ファイル: GrCCPathParser.cpp プロジェクト: molikto/Skia
void GrCCPathParser::parsePath(const SkPath& path, const SkPoint* deviceSpacePts) {
    SkASSERT(!fInstanceBuffer); // Can't call after finalize().
    SkASSERT(!fParsingPath); // Call saveParsedPath() or discardParsedPath() for the last one first.
    SkDEBUGCODE(fParsingPath = true);
    SkASSERT(path.isEmpty() || deviceSpacePts);

    fCurrPathPointsIdx = fGeometry.points().count();
    fCurrPathVerbsIdx = fGeometry.verbs().count();
    fCurrPathPrimitiveCounts = PrimitiveTallies();

    fGeometry.beginPath();

    if (path.isEmpty()) {
        return;
    }

    const float* conicWeights = SkPathPriv::ConicWeightData(path);
    int ptsIdx = 0;
    int conicWeightsIdx = 0;
    bool insideContour = false;

    for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
        switch (verb) {
            case SkPath::kMove_Verb:
                this->endContourIfNeeded(insideContour);
                fGeometry.beginContour(deviceSpacePts[ptsIdx]);
                ++ptsIdx;
                insideContour = true;
                continue;
            case SkPath::kClose_Verb:
                this->endContourIfNeeded(insideContour);
                insideContour = false;
                continue;
            case SkPath::kLine_Verb:
                fGeometry.lineTo(&deviceSpacePts[ptsIdx - 1]);
                ++ptsIdx;
                continue;
            case SkPath::kQuad_Verb:
                fGeometry.quadraticTo(&deviceSpacePts[ptsIdx - 1]);
                ptsIdx += 2;
                continue;
            case SkPath::kCubic_Verb:
                fGeometry.cubicTo(&deviceSpacePts[ptsIdx - 1]);
                ptsIdx += 3;
                continue;
            case SkPath::kConic_Verb:
                fGeometry.conicTo(&deviceSpacePts[ptsIdx - 1], conicWeights[conicWeightsIdx]);
                ptsIdx += 2;
                ++conicWeightsIdx;
                continue;
            default:
                SK_ABORT("Unexpected path verb.");
        }
    }
    SkASSERT(ptsIdx == path.countPoints());
    SkASSERT(conicWeightsIdx == SkPathPriv::ConicWeightCnt(path));

    this->endContourIfNeeded(insideContour);
}
コード例 #7
0
ファイル: GrCCPathParser.cpp プロジェクト: molikto/Skia
void GrCCPathParser::parsePath(const SkMatrix& m, const SkPath& path, SkRect* devBounds,
                               SkRect* devBounds45) {
    const SkPoint* pts = SkPathPriv::PointData(path);
    int numPts = path.countPoints();
    SkASSERT(numPts + 1 <= fLocalDevPtsBuffer.count());

    if (!numPts) {
        devBounds->setEmpty();
        devBounds45->setEmpty();
        this->parsePath(path, nullptr);
        return;
    }

    // m45 transforms path points into "45 degree" device space. A bounding box in this space gives
    // the circumscribing octagon's diagonals. We could use SK_ScalarRoot2Over2, but an orthonormal
    // transform is not necessary as long as the shader uses the correct inverse.
    SkMatrix m45;
    m45.setSinCos(1, 1);
    m45.preConcat(m);

    // X,Y,T are two parallel view matrices that accumulate two bounding boxes as they map points:
    // device-space bounds and "45 degree" device-space bounds (| 1 -1 | * devCoords).
    //                                                          | 1  1 |
    Sk4f X = Sk4f(m.getScaleX(), m.getSkewY(), m45.getScaleX(), m45.getSkewY());
    Sk4f Y = Sk4f(m.getSkewX(), m.getScaleY(), m45.getSkewX(), m45.getScaleY());
    Sk4f T = Sk4f(m.getTranslateX(), m.getTranslateY(), m45.getTranslateX(), m45.getTranslateY());

    // Map the path's points to device space and accumulate bounding boxes.
    Sk4f devPt = SkNx_fma(Y, Sk4f(pts[0].y()), T);
    devPt = SkNx_fma(X, Sk4f(pts[0].x()), devPt);
    Sk4f topLeft = devPt;
    Sk4f bottomRight = devPt;

    // Store all 4 values [dev.x, dev.y, dev45.x, dev45.y]. We are only interested in the first two,
    // and will overwrite [dev45.x, dev45.y] with the next point. This is why the dst buffer must
    // be at least one larger than the number of points.
    devPt.store(&fLocalDevPtsBuffer[0]);

    for (int i = 1; i < numPts; ++i) {
        devPt = SkNx_fma(Y, Sk4f(pts[i].y()), T);
        devPt = SkNx_fma(X, Sk4f(pts[i].x()), devPt);
        topLeft = Sk4f::Min(topLeft, devPt);
        bottomRight = Sk4f::Max(bottomRight, devPt);
        devPt.store(&fLocalDevPtsBuffer[i]);
    }

    SkPoint topLeftPts[2], bottomRightPts[2];
    topLeft.store(topLeftPts);
    bottomRight.store(bottomRightPts);
    devBounds->setLTRB(topLeftPts[0].x(), topLeftPts[0].y(), bottomRightPts[0].x(),
                       bottomRightPts[0].y());
    devBounds45->setLTRB(topLeftPts[1].x(), topLeftPts[1].y(), bottomRightPts[1].x(),
                         bottomRightPts[1].y());

    this->parsePath(path, fLocalDevPtsBuffer.get());
}
コード例 #8
0
ファイル: SkGlyphCache.cpp プロジェクト: vschs007/skia
const SkPath* SkGlyphCache::findPath(const SkGlyph& glyph) {
    if (glyph.fWidth) {
        if (glyph.fPathData == nullptr) {
            SkGlyph::PathData* pathData = fAlloc.make<SkGlyph::PathData>();
            const_cast<SkGlyph&>(glyph).fPathData = pathData;
            pathData->fIntercept = nullptr;
            SkPath* path = pathData->fPath = new SkPath;
            fScalerContext->getPath(glyph.getPackedID(), path);
            fMemoryUsed += sizeof(SkPath) + path->countPoints() * sizeof(SkPoint);
        }
    }
    return glyph.fPathData ? glyph.fPathData->fPath : nullptr;
}
コード例 #9
0
ファイル: GrShape.cpp プロジェクト: molikto/Skia
// If the path is small enough to be keyed from its data this returns key length, otherwise -1.
static int path_key_from_data_size(const SkPath& path) {
    const int verbCnt = path.countVerbs();
    if (verbCnt > GrShape::kMaxKeyFromDataVerbCnt) {
        return -1;
    }
    const int pointCnt = path.countPoints();
    const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path);

    GR_STATIC_ASSERT(sizeof(SkPoint) == 2 * sizeof(uint32_t));
    GR_STATIC_ASSERT(sizeof(SkScalar) == sizeof(uint32_t));
    // 2 is for the verb cnt and a fill type. Each verb is a byte but we'll pad the verb data out to
    // a uint32_t length.
    return 2 + (SkAlign4(verbCnt) >> 2) + 2 * pointCnt + conicWeightCnt;
}
コード例 #10
0
ファイル: EmptyPathTest.cpp プロジェクト: Adenilson/skia
static void drawAndTest(skiatest::Reporter* reporter, const SkPath& path,
                        const SkPaint& paint, bool shouldDraw) {
    SkBitmap bm;
    bm.allocN32Pixels(DIMENSION, DIMENSION);
    SkASSERT(DIMENSION*4 == bm.rowBytes()); // ensure no padding on each row
    bm.eraseColor(SK_ColorTRANSPARENT);

    SkCanvas canvas(bm);
    SkPaint p(paint);
    p.setColor(SK_ColorWHITE);

    canvas.drawPath(path, p);

    size_t count = DIMENSION * DIMENSION;
    const SkPMColor* ptr = bm.getAddr32(0, 0);

    SkPMColor andValue = ~0U;
    SkPMColor orValue = 0;
    for (size_t i = 0; i < count; ++i) {
        SkPMColor c = ptr[i];
        andValue &= c;
        orValue |= c;
    }

    // success means we drew everywhere or nowhere (depending on shouldDraw)
    bool success = shouldDraw ? (~0U == andValue) : (0 == orValue);

    if (!success) {
        const char* str;
        if (shouldDraw) {
            str = "Path expected to draw everywhere, but didn't. ";
        } else {
            str = "Path expected to draw nowhere, but did. ";
        }
        ERRORF(reporter, "%s style[%d] cap[%d] join[%d] antialias[%d]"
               " filltype[%d] ptcount[%d]", str, paint.getStyle(),
               paint.getStrokeCap(), paint.getStrokeJoin(),
               paint.isAntiAlias(), path.getFillType(), path.countPoints());
// uncomment this if you want to step in to see the failure
//        canvas.drawPath(path, p);
    }
}
コード例 #11
0
ファイル: GrGLPath.cpp プロジェクト: nicholas-yangding/skia
void GrGLPath::InitPathObjectPathData(GrGLGpu* gpu,
                                      GrGLuint pathID,
                                      const SkPath& skPath) {
    SkASSERT(!skPath.isEmpty());

#ifdef SK_SCALAR_IS_FLOAT
    // This branch does type punning, converting SkPoint* to GrGLfloat*.
    if ((skPath.getSegmentMasks() & SkPath::kConic_SegmentMask) == 0) {
        int verbCnt = skPath.countVerbs();
        int pointCnt = skPath.countPoints();
        int coordCnt = pointCnt * 2;
        SkSTArray<16, GrGLubyte, true> pathCommands(verbCnt);
        SkSTArray<16, GrGLfloat, true> pathCoords(coordCnt);

        static_assert(sizeof(SkPoint) == sizeof(GrGLfloat) * 2, "sk_point_not_two_floats");

        pathCommands.resize_back(verbCnt);
        pathCoords.resize_back(coordCnt);
        skPath.getPoints(reinterpret_cast<SkPoint*>(&pathCoords[0]), pointCnt);
        skPath.getVerbs(&pathCommands[0], verbCnt);

        SkDEBUGCODE(int verbCoordCnt = 0);
        for (int i = 0; i < verbCnt; ++i) {
            SkPath::Verb v = static_cast<SkPath::Verb>(pathCommands[i]);
            pathCommands[i] = verb_to_gl_path_cmd(v);
            SkDEBUGCODE(verbCoordCnt += num_coords(v));
        }
        SkASSERT(verbCnt == pathCommands.count());
        SkASSERT(verbCoordCnt == pathCoords.count());
        SkDEBUGCODE(verify_floats(&pathCoords[0], pathCoords.count()));
        GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, pathCommands.count(), &pathCommands[0],
                                                    pathCoords.count(), GR_GL_FLOAT,
                                                    &pathCoords[0]));
        return;
    }
#endif
    SkAssertResult(init_path_object_for_general_path<false>(gpu, pathID, skPath));
}
コード例 #12
0
ファイル: strokerect.cpp プロジェクト: AHPlankton/skia
static void draw_path(SkCanvas* canvas, const SkPath& path, const SkRect& rect,
                      SkPaint::Join join, int doFill) {
    SkPaint paint;
    paint.setAntiAlias(true);
    paint.setStyle(doFill ? SkPaint::kStrokeAndFill_Style : SkPaint::kStroke_Style);

    paint.setColor(sk_tool_utils::color_to_565(SK_ColorGRAY));
    paint.setStrokeWidth(STROKE_WIDTH);
    paint.setStrokeJoin(join);
    canvas->drawRect(rect, paint);

    paint.setStyle(SkPaint::kStroke_Style);
    paint.setStrokeWidth(0);
    paint.setColor(SK_ColorRED);
    canvas->drawPath(path, paint);

    paint.setStrokeWidth(3);
    paint.setStrokeJoin(SkPaint::kMiter_Join);
    int n = path.countPoints();
    SkAutoTArray<SkPoint> points(n);
    path.getPoints(points.get(), n);
    canvas->drawPoints(SkCanvas::kPoints_PointMode, n, points.get(), paint);
}
コード例 #13
0
ファイル: SampleQuadStroker.cpp プロジェクト: Arternis/skia
 void draw_points(SkCanvas* canvas, const SkPath& path, SkColor color,
                  bool show_lines) {
     SkPaint paint;
     paint.setColor(color);
     paint.setAlpha(0x80);
     paint.setAntiAlias(true);
     int n = path.countPoints();
     SkAutoSTArray<32, SkPoint> pts(n);
     if (show_lines && fDrawTangents) {
         SkTArray<int> contourCounts;
         getContourCounts(path, &contourCounts);
         SkPoint* ptPtr = pts.get();
         for (int i = 0; i < contourCounts.count(); ++i) {
             int count = contourCounts[i];
             path.getPoints(ptPtr, count);
             canvas->drawPoints(SkCanvas::kPolygon_PointMode, count, ptPtr, paint);
             ptPtr += count;
         }
     } else {
         n = getOnCurvePoints(path, pts.get());
     }
     paint.setStrokeWidth(5);
     canvas->drawPoints(SkCanvas::kPoints_PointMode, n, pts.get(), paint);
 }
コード例 #14
0
void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
    SkASSERT(&src != NULL && dst != NULL);

    SkScalar radius = SkScalarHalf(fWidth);

    dst->reset();
    if (radius <= 0) {
        return;
    }
    
#ifdef SK_SCALAR_IS_FIXED
    void (*proc)(SkPoint pts[], int count) = identity_proc;
    if (needs_to_shrink(src)) {
        proc = shift_down_2_proc;
        radius >>= 2;
        if (radius == 0) {
            return;
        }
    }
#endif

    SkPathStroker   stroker(radius, fMiterLimit, this->getCap(),
                            this->getJoin());

    SkPath::Iter    iter(src, false);
    SkPoint         pts[4];
    SkPath::Verb    verb, lastSegment = SkPath::kMove_Verb;

    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
        switch (verb) {
            case SkPath::kMove_Verb:
                APPLY_PROC(proc, &pts[0], 1);
                stroker.moveTo(pts[0]);
                break;
            case SkPath::kLine_Verb:
                APPLY_PROC(proc, &pts[1], 1);
                stroker.lineTo(pts[1]);
                lastSegment = verb;
                break;
            case SkPath::kQuad_Verb:
                APPLY_PROC(proc, &pts[1], 2);
                stroker.quadTo(pts[1], pts[2]);
                lastSegment = verb;
                break;
            case SkPath::kCubic_Verb:
                APPLY_PROC(proc, &pts[1], 3);
                stroker.cubicTo(pts[1], pts[2], pts[3]);
                lastSegment = verb;
                break;
            case SkPath::kClose_Verb:
                stroker.close(lastSegment == SkPath::kLine_Verb);
                break;
            default:
                break;
        }
    }
    stroker.done(dst, lastSegment == SkPath::kLine_Verb);

#ifdef SK_SCALAR_IS_FIXED
    // undo our previous down_shift
    if (shift_down_2_proc == proc) {
        // need a real shift methid on path. antialias paths could use this too
        SkMatrix matrix;
        matrix.setScale(SkIntToScalar(4), SkIntToScalar(4));
        dst->transform(matrix);
    }
#endif

    if (fDoFill) {
        if (src.cheapIsDirection(SkPath::kCCW_Direction)) {
            dst->reverseAddPath(src);
        } else {
            dst->addPath(src);
        }
    } else {
        //  Seems like we can assume that a 2-point src would always result in
        //  a convex stroke, but testing has proved otherwise.
        //  TODO: fix the stroker to make this assumption true (without making
        //  it slower that the work that will be done in computeConvexity())
#if 0
        // this test results in a non-convex stroke :(
        static void test(SkCanvas* canvas) {
            SkPoint pts[] = { 146.333328,  192.333328, 300.333344, 293.333344 };
            SkPaint paint;
            paint.setStrokeWidth(7);
            paint.setStrokeCap(SkPaint::kRound_Cap);
            canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
        }        
#endif
#if 0
        if (2 == src.countPoints()) {
            dst->setIsConvex(true);
        }
#endif
    }
void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
    SkASSERT(&src != NULL && dst != NULL);

    SkScalar radius = SkScalarHalf(fWidth);

    AutoTmpPath tmp(src, &dst);

    if (radius <= 0) {
        return;
    }

    // If src is really a rect, call our specialty strokeRect() method
    {
        bool isClosed;
        SkPath::Direction dir;
        if (src.isRect(&isClosed, &dir) && isClosed) {
            this->strokeRect(src.getBounds(), dst, dir);
            // our answer should preserve the inverseness of the src
            if (src.isInverseFillType()) {
                SkASSERT(!dst->isInverseFillType());
                dst->toggleInverseFillType();
            }
            return;
        }
    }

    SkAutoConicToQuads converter;
    const SkScalar conicTol = SK_Scalar1 / 4;

    SkPathStroker   stroker(src, radius, fMiterLimit, this->getCap(),
                            this->getJoin());
    SkPath::Iter    iter(src, false);
    SkPath::Verb    lastSegment = SkPath::kMove_Verb;

    for (;;) {
        SkPoint  pts[4];
        switch (iter.next(pts, false)) {
            case SkPath::kMove_Verb:
                stroker.moveTo(pts[0]);
                break;
            case SkPath::kLine_Verb:
                stroker.lineTo(pts[1]);
                lastSegment = SkPath::kLine_Verb;
                break;
            case SkPath::kQuad_Verb:
                stroker.quadTo(pts[1], pts[2]);
                lastSegment = SkPath::kQuad_Verb;
                break;
            case SkPath::kConic_Verb: {
                // todo: if we had maxcurvature for conics, perhaps we should
                // natively extrude the conic instead of converting to quads.
                const SkPoint* quadPts =
                    converter.computeQuads(pts, iter.conicWeight(), conicTol);
                for (int i = 0; i < converter.countQuads(); ++i) {
                    stroker.quadTo(quadPts[1], quadPts[2]);
                    quadPts += 2;
                }
                lastSegment = SkPath::kQuad_Verb;
            } break;
            case SkPath::kCubic_Verb:
                stroker.cubicTo(pts[1], pts[2], pts[3]);
                lastSegment = SkPath::kCubic_Verb;
                break;
            case SkPath::kClose_Verb:
                stroker.close(lastSegment == SkPath::kLine_Verb);
                break;
            case SkPath::kDone_Verb:
                goto DONE;
        }
    }
DONE:
    stroker.done(dst, lastSegment == SkPath::kLine_Verb);

    if (fDoFill) {
        if (src.cheapIsDirection(SkPath::kCCW_Direction)) {
            dst->reverseAddPath(src);
        } else {
            dst->addPath(src);
        }
    } else {
        //  Seems like we can assume that a 2-point src would always result in
        //  a convex stroke, but testing has proved otherwise.
        //  TODO: fix the stroker to make this assumption true (without making
        //  it slower that the work that will be done in computeConvexity())
#if 0
        // this test results in a non-convex stroke :(
        static void test(SkCanvas* canvas) {
            SkPoint pts[] = { 146.333328,  192.333328, 300.333344, 293.333344 };
            SkPaint paint;
            paint.setStrokeWidth(7);
            paint.setStrokeCap(SkPaint::kRound_Cap);
            canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
        }
#endif
#if 0
        if (2 == src.countPoints()) {
            dst->setIsConvex(true);
        }
#endif
    }
コード例 #16
0
ファイル: GrCCFiller.cpp プロジェクト: jasonLaster/gecko-dev
void GrCCFiller::parseDeviceSpaceFill(const SkPath& path, const SkPoint* deviceSpacePts,
                                      GrScissorTest scissorTest, const SkIRect& clippedDevIBounds,
                                      const SkIVector& devToAtlasOffset) {
    SkASSERT(!fInstanceBuffer);  // Can't call after prepareToDraw().
    SkASSERT(!path.isEmpty());

    int currPathPointsIdx = fGeometry.points().count();
    int currPathVerbsIdx = fGeometry.verbs().count();
    PrimitiveTallies currPathPrimitiveCounts = PrimitiveTallies();

    fGeometry.beginPath();

    const float* conicWeights = SkPathPriv::ConicWeightData(path);
    int ptsIdx = 0;
    int conicWeightsIdx = 0;
    bool insideContour = false;

    for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
        switch (verb) {
            case SkPath::kMove_Verb:
                if (insideContour) {
                    currPathPrimitiveCounts += fGeometry.endContour();
                }
                fGeometry.beginContour(deviceSpacePts[ptsIdx]);
                ++ptsIdx;
                insideContour = true;
                continue;
            case SkPath::kClose_Verb:
                if (insideContour) {
                    currPathPrimitiveCounts += fGeometry.endContour();
                }
                insideContour = false;
                continue;
            case SkPath::kLine_Verb:
                fGeometry.lineTo(&deviceSpacePts[ptsIdx - 1]);
                ++ptsIdx;
                continue;
            case SkPath::kQuad_Verb:
                fGeometry.quadraticTo(&deviceSpacePts[ptsIdx - 1]);
                ptsIdx += 2;
                continue;
            case SkPath::kCubic_Verb:
                fGeometry.cubicTo(&deviceSpacePts[ptsIdx - 1]);
                ptsIdx += 3;
                continue;
            case SkPath::kConic_Verb:
                fGeometry.conicTo(&deviceSpacePts[ptsIdx - 1], conicWeights[conicWeightsIdx]);
                ptsIdx += 2;
                ++conicWeightsIdx;
                continue;
            default:
                SK_ABORT("Unexpected path verb.");
        }
    }
    SkASSERT(ptsIdx == path.countPoints());
    SkASSERT(conicWeightsIdx == SkPathPriv::ConicWeightCnt(path));

    if (insideContour) {
        currPathPrimitiveCounts += fGeometry.endContour();
    }

    fPathInfos.emplace_back(scissorTest, devToAtlasOffset);

    // Tessellate fans from very large and/or simple paths, in order to reduce overdraw.
    int numVerbs = fGeometry.verbs().count() - currPathVerbsIdx - 1;
    int64_t tessellationWork = (int64_t)numVerbs * (32 - SkCLZ(numVerbs)); // N log N.
    int64_t fanningWork = (int64_t)clippedDevIBounds.height() * clippedDevIBounds.width();
    if (tessellationWork * (50*50) + (100*100) < fanningWork) { // Don't tessellate under 100x100.
        fPathInfos.back().tessellateFan(fGeometry, currPathVerbsIdx, currPathPointsIdx,
                                        clippedDevIBounds, &currPathPrimitiveCounts);
    }

    fTotalPrimitiveCounts[(int)scissorTest] += currPathPrimitiveCounts;

    if (GrScissorTest::kEnabled == scissorTest) {
        fScissorSubBatches.push_back() = {fTotalPrimitiveCounts[(int)GrScissorTest::kEnabled],
                                          clippedDevIBounds.makeOffset(devToAtlasOffset.fX,
                                                                       devToAtlasOffset.fY)};
    }
}
コード例 #17
0
static void test_zero_length_paths(skiatest::Reporter* reporter) {
    SkPath p;
    SkRect bounds;

    // Lone moveTo case
    p.moveTo(SK_Scalar1, SK_Scalar1);
    bounds.set(0, 0, 0, 0);
    REPORTER_ASSERT(reporter, !p.isEmpty());
    REPORTER_ASSERT(reporter, 1 == p.countPoints());
    REPORTER_ASSERT(reporter, bounds == p.getBounds());

    // MoveTo-MoveTo case
    p.moveTo(SK_Scalar1*2, SK_Scalar1);
    bounds.set(SK_Scalar1, SK_Scalar1, 2*SK_Scalar1, SK_Scalar1);
    REPORTER_ASSERT(reporter, !p.isEmpty());
    REPORTER_ASSERT(reporter, 2 == p.countPoints());
    REPORTER_ASSERT(reporter, bounds == p.getBounds()); 

    // moveTo-close case
    p.reset();
    p.moveTo(SK_Scalar1, SK_Scalar1);
    p.close();
    bounds.set(0, 0, 0, 0);
    REPORTER_ASSERT(reporter, !p.isEmpty());
    REPORTER_ASSERT(reporter, 1 == p.countPoints());
    REPORTER_ASSERT(reporter, bounds == p.getBounds());

    // moveTo-close-moveTo-close case
    p.moveTo(SK_Scalar1*2, SK_Scalar1);
    p.close();
    bounds.set(SK_Scalar1, SK_Scalar1, 2*SK_Scalar1, SK_Scalar1);
    REPORTER_ASSERT(reporter, !p.isEmpty());
    REPORTER_ASSERT(reporter, 2 == p.countPoints());
    REPORTER_ASSERT(reporter, bounds == p.getBounds());

    // moveTo-line case
    p.reset();
    p.moveTo(SK_Scalar1, SK_Scalar1);
    p.lineTo(SK_Scalar1, SK_Scalar1);
    bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1);
    REPORTER_ASSERT(reporter, !p.isEmpty());
    REPORTER_ASSERT(reporter, 2 == p.countPoints());
    REPORTER_ASSERT(reporter, bounds == p.getBounds());

    // moveTo-lineTo-moveTo-lineTo case
    p.moveTo(SK_Scalar1*2, SK_Scalar1);
    p.lineTo(SK_Scalar1*2, SK_Scalar1);
    bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1*2, SK_Scalar1);
    REPORTER_ASSERT(reporter, !p.isEmpty());
    REPORTER_ASSERT(reporter, 4 == p.countPoints());
    REPORTER_ASSERT(reporter, bounds == p.getBounds());

    // moveTo-line-close case
    p.reset();
    p.moveTo(SK_Scalar1, SK_Scalar1);
    p.lineTo(SK_Scalar1, SK_Scalar1);
    p.close();
    bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1);
    REPORTER_ASSERT(reporter, !p.isEmpty());
    REPORTER_ASSERT(reporter, 2 == p.countPoints());
    REPORTER_ASSERT(reporter, bounds == p.getBounds());

    // moveTo-line-close-moveTo-line-close case
    p.moveTo(SK_Scalar1*2, SK_Scalar1);
    p.lineTo(SK_Scalar1*2, SK_Scalar1);
    p.close();
    bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1*2, SK_Scalar1);
    REPORTER_ASSERT(reporter, !p.isEmpty());
    REPORTER_ASSERT(reporter, 4 == p.countPoints());
    REPORTER_ASSERT(reporter, bounds == p.getBounds());

    // moveTo-quadTo case
    p.reset();
    p.moveTo(SK_Scalar1, SK_Scalar1);
    p.quadTo(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1);
    bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1);
    REPORTER_ASSERT(reporter, !p.isEmpty());
    REPORTER_ASSERT(reporter, 3 == p.countPoints());
    REPORTER_ASSERT(reporter, bounds == p.getBounds());

    // moveTo-quadTo-close case
    p.close();
    REPORTER_ASSERT(reporter, !p.isEmpty());
    REPORTER_ASSERT(reporter, 3 == p.countPoints());
    REPORTER_ASSERT(reporter, bounds == p.getBounds());

    // moveTo-quadTo-moveTo-quadTo case
    p.reset();
    p.moveTo(SK_Scalar1, SK_Scalar1);
    p.quadTo(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1);
    p.moveTo(SK_Scalar1*2, SK_Scalar1);
    p.quadTo(SK_Scalar1*2, SK_Scalar1, SK_Scalar1*2, SK_Scalar1);
    bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1*2, SK_Scalar1);
    REPORTER_ASSERT(reporter, !p.isEmpty());
    REPORTER_ASSERT(reporter, 6 == p.countPoints());
    REPORTER_ASSERT(reporter, bounds == p.getBounds());

    // moveTo-cubicTo case
    p.reset();
    p.moveTo(SK_Scalar1, SK_Scalar1);
    p.cubicTo(SK_Scalar1, SK_Scalar1,
              SK_Scalar1, SK_Scalar1,
              SK_Scalar1, SK_Scalar1);
    bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1);
    REPORTER_ASSERT(reporter, !p.isEmpty());
    REPORTER_ASSERT(reporter, 4 == p.countPoints());
    REPORTER_ASSERT(reporter, bounds == p.getBounds());

    // moveTo-quadTo-close case
    p.close();
    REPORTER_ASSERT(reporter, !p.isEmpty());
    REPORTER_ASSERT(reporter, 4 == p.countPoints());
    REPORTER_ASSERT(reporter, bounds == p.getBounds());

    // moveTo-quadTo-moveTo-quadTo case
    p.reset();
    p.moveTo(SK_Scalar1, SK_Scalar1);
    p.cubicTo(SK_Scalar1, SK_Scalar1,
              SK_Scalar1, SK_Scalar1,
              SK_Scalar1, SK_Scalar1);
    p.moveTo(SK_Scalar1*2, SK_Scalar1);
    p.cubicTo(SK_Scalar1*2, SK_Scalar1,
              SK_Scalar1*2, SK_Scalar1,
              SK_Scalar1*2, SK_Scalar1);
    bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1*2, SK_Scalar1);
    REPORTER_ASSERT(reporter, !p.isEmpty());
    REPORTER_ASSERT(reporter, 8 == p.countPoints());
    REPORTER_ASSERT(reporter, bounds == p.getBounds());
}
コード例 #18
0
void GrGLPath::InitPathObject(GrGLGpu* gpu,
                              GrGLuint pathID,
                              const SkPath& skPath,
                              const GrStrokeInfo& stroke) {
    SkASSERT(!stroke.isDashed());
    if (!skPath.isEmpty()) {
        int verbCnt = skPath.countVerbs();
        int pointCnt = skPath.countPoints();
        int minCoordCnt = pointCnt * 2;

        SkSTArray<16, GrGLubyte, true> pathCommands(verbCnt);
        SkSTArray<16, GrGLfloat, true> pathCoords(minCoordCnt);

        SkDEBUGCODE(int numCoords = 0);

        if ((skPath.getSegmentMasks() & SkPath::kConic_SegmentMask) == 0) {
            // This branch does type punning, converting SkPoint* to GrGLfloat*.
            SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(GrGLfloat) * 2, sk_point_not_two_floats);
            // This branch does not convert with SkScalarToFloat.
#ifndef SK_SCALAR_IS_FLOAT
#error Need SK_SCALAR_IS_FLOAT.
#endif
            pathCommands.resize_back(verbCnt);
            pathCoords.resize_back(minCoordCnt);
            skPath.getPoints(reinterpret_cast<SkPoint*>(&pathCoords[0]), pointCnt);
            skPath.getVerbs(&pathCommands[0], verbCnt);
            for (int i = 0; i < verbCnt; ++i) {
                SkPath::Verb v = static_cast<SkPath::Verb>(pathCommands[i]);
                pathCommands[i] = verb_to_gl_path_cmd(v);
                SkDEBUGCODE(numCoords += num_coords(v));
            }
        } else {
            SkPoint points[4];
            SkPath::RawIter iter(skPath);
            SkPath::Verb verb;
            while ((verb = iter.next(points)) != SkPath::kDone_Verb) {
                pathCommands.push_back(verb_to_gl_path_cmd(verb));
                GrGLfloat coords[6];
                int coordsForVerb;
                switch (verb) {
                    case SkPath::kMove_Verb:
                        points_to_coords(points, 0, 1, coords);
                        coordsForVerb = 2;
                        break;
                    case SkPath::kLine_Verb:
                        points_to_coords(points, 1, 1, coords);
                        coordsForVerb = 2;
                        break;
                    case SkPath::kConic_Verb:
                        points_to_coords(points, 1, 2, coords);
                        coords[4] = SkScalarToFloat(iter.conicWeight());
                        coordsForVerb = 5;
                        break;
                    case SkPath::kQuad_Verb:
                        points_to_coords(points, 1, 2, coords);
                        coordsForVerb = 4;
                        break;
                    case SkPath::kCubic_Verb:
                        points_to_coords(points, 1, 3, coords);
                        coordsForVerb = 6;
                        break;
                    case SkPath::kClose_Verb:
                        continue;
                    default:
                        SkASSERT(false);  // Not reached.
                        continue;
                }
                SkDEBUGCODE(numCoords += num_coords(verb));
                pathCoords.push_back_n(coordsForVerb, coords);
            }
        }

        SkASSERT(verbCnt == pathCommands.count());
        SkASSERT(numCoords == pathCoords.count());

        GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, pathCommands.count(), &pathCommands[0],
                   pathCoords.count(), GR_GL_FLOAT, &pathCoords[0]));
    } else {
        GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, 0, NULL, 0, GR_GL_FLOAT, NULL));
    }

    if (stroke.needToApply()) {
        SkASSERT(!stroke.isHairlineStyle());
        GR_GL_CALL(gpu->glInterface(),
            PathParameterf(pathID, GR_GL_PATH_STROKE_WIDTH, SkScalarToFloat(stroke.getWidth())));
        GR_GL_CALL(gpu->glInterface(),
            PathParameterf(pathID, GR_GL_PATH_MITER_LIMIT, SkScalarToFloat(stroke.getMiter())));
        GrGLenum join = join_to_gl_join(stroke.getJoin());
        GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_JOIN_STYLE, join));
        GrGLenum cap = cap_to_gl_cap(stroke.getCap());
        GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_END_CAPS, cap));
        GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_STROKE_BOUND, 0.02f));
    }
}
コード例 #19
0
bool GrStrokePathRenderer::onDrawPath(const SkPath& origPath,
                                      const SkStrokeRec& stroke,
                                      GrDrawTarget* target,
                                      bool antiAlias) {
    if (origPath.isEmpty()) {
        return true;
    }

    SkScalar width = stroke.getWidth();
    if (width <= 0) {
        return false;
    }

    // Get the join type
    SkPaint::Join join = stroke.getJoin();
    SkScalar miterLimit = stroke.getMiter();
    SkScalar sqMiterLimit = SkScalarMul(miterLimit, miterLimit);
    if ((join == SkPaint::kMiter_Join) && (miterLimit <= SK_Scalar1)) {
        // If the miter limit is small, treat it as a bevel join
        join = SkPaint::kBevel_Join;
    }
    const bool isMiter       = (join == SkPaint::kMiter_Join);
    const bool isBevel       = (join == SkPaint::kBevel_Join);
    SkScalar invMiterLimit   = isMiter ? SK_Scalar1 / miterLimit : 0;
    SkScalar invMiterLimitSq = SkScalarMul(invMiterLimit, invMiterLimit);

    // Allocate vertices
    const int nbQuads     = origPath.countPoints() + 1; // Could be "-1" if path is not closed
    const int extraVerts  = isMiter || isBevel ? 1 : 0;
    const int maxVertexCount = nbQuads * (4 + extraVerts);
    const int maxIndexCount  = nbQuads * (6 + extraVerts * 3); // Each extra vert adds a triangle
    target->drawState()->setDefaultVertexAttribs();
    GrDrawTarget::AutoReleaseGeometry arg(target, maxVertexCount, maxIndexCount);
    if (!arg.succeeded()) {
        return false;
    }
    SkPoint* verts = reinterpret_cast<SkPoint*>(arg.vertices());
    uint16_t* idxs = reinterpret_cast<uint16_t*>(arg.indices());
    int vCount = 0, iCount = 0;

    // Transform the path into a list of triangles
    SkPath::Iter iter(origPath, false);
    SkPoint pts[4];
    const SkScalar radius = SkScalarMul(width, 0.5f);
    SkPoint *firstPt = verts, *lastPt = NULL;
    SkVector firstDir, dir;
    firstDir.set(0, 0);
    dir.set(0, 0);
    bool isOpen = true;
    for(SkPath::Verb v = iter.next(pts); v != SkPath::kDone_Verb; v = iter.next(pts)) {
        switch(v) {
            case SkPath::kMove_Verb:
                // This will already be handled as pts[0] of the 1st line
                break;
            case SkPath::kClose_Verb:
                isOpen = (lastPt == NULL);
                break;
            case SkPath::kLine_Verb:
            {
                SkVector v0 = dir;
                dir = pts[1] - pts[0];
                if (dir.setLength(radius)) {
                    SkVector dirT;
                    dirT.set(dir.fY, -dir.fX); // Get perpendicular direction
                    SkPoint l1a = pts[0]+dirT, l1b = pts[1]+dirT,
                            l2a = pts[0]-dirT, l2b = pts[1]-dirT;
                    SkPoint miterPt[2];
                    bool useMiterPoint = false;
                    int idx0(-1), idx1(-1);
                    if (NULL == lastPt) {
                        firstDir = dir;
                    } else {
                        SkVector v1 = dir;
                        if (v0.normalize() && v1.normalize()) {
                            SkScalar dotProd = v0.dot(v1);
                            // No need for bevel or miter join if the angle
                            // is either 0 or 180 degrees
                            if (!SkScalarNearlyZero(dotProd + SK_Scalar1) &&
                                !SkScalarNearlyZero(dotProd - SK_Scalar1)) {
                                bool ccw = !is_clockwise(v0, v1);
                                int offset = ccw ? 1 : 0;
                                idx0 = vCount-2+offset;
                                idx1 = vCount+offset;
                                const SkPoint* pt0 = &(lastPt[offset]);
                                const SkPoint* pt1 = ccw ? &l2a : &l1a;
                                switch(join) {
                                    case SkPaint::kMiter_Join:
                                    {
                                        // *Note : Logic is from MiterJoiner

                                        // FIXME : Special case if we have a right angle ?
                                        // if (SkScalarNearlyZero(dotProd)) {...}

                                        SkScalar sinHalfAngleSq =
                                                SkScalarHalf(SK_Scalar1 + dotProd);
                                        if (sinHalfAngleSq >= invMiterLimitSq) {
                                            // Find the miter point (or points if it is further
                                            // than the miter limit)
                                            const SkPoint pt2 = *pt0+v0, pt3 = *pt1+v1;
                                            if (intersection(*pt0, pt2, *pt1, pt3, miterPt[0]) !=
                                                kNone_IntersectionType) {
                                                SkPoint miterPt0 = miterPt[0] - *pt0;
                                                SkPoint miterPt1 = miterPt[0] - *pt1;
                                                SkScalar sqDist0 = miterPt0.dot(miterPt0);
                                                SkScalar sqDist1 = miterPt1.dot(miterPt1);
                                                const SkScalar rSq =
                                                        SkScalarDiv(SkScalarMul(radius, radius),
                                                                    sinHalfAngleSq);
                                                const SkScalar sqRLimit =
                                                        SkScalarMul(sqMiterLimit, rSq);
                                                if (sqDist0 > sqRLimit || sqDist1 > sqRLimit) {
                                                    if (sqDist1 > sqRLimit) {
                                                        v1.setLength(SkScalarSqrt(sqRLimit));
                                                        miterPt[1] = *pt1+v1;
                                                    } else {
                                                        miterPt[1] = miterPt[0];
                                                    }
                                                    if (sqDist0 > sqRLimit) {
                                                        v0.setLength(SkScalarSqrt(sqRLimit));
                                                        miterPt[0] = *pt0+v0;
                                                    }
                                                } else {
                                                    miterPt[1] = miterPt[0];
                                                }
                                                useMiterPoint = true;
                                            }
                                        }
                                        if (useMiterPoint && (miterPt[1] == miterPt[0])) {
                                            break;
                                        }
                                    }
                                    default:
                                    case SkPaint::kBevel_Join:
                                    {
                                        // Note : This currently causes some overdraw where both
                                        //        lines initially intersect. We'd need to add
                                        //        another line intersection check here if the
                                        //        overdraw becomes an issue instead of using the
                                        //        current point directly.

                                        // Add center point
                                        *verts++ = pts[0]; // Use current point directly
                                        // This idx is passed the current point so increment it
                                        ++idx1;
                                        // Add center triangle
                                        *idxs++ = idx0;
                                        *idxs++ = vCount;
                                        *idxs++ = idx1;
                                        vCount++;
                                        iCount += 3;
                                    }
                                    break;
                                }
                            }
                        }
                    }
                    *verts++ = l1a;
                    *verts++ = l2a;
                    lastPt   = verts;
                    *verts++ = l1b;
                    *verts++ = l2b;

                    if (useMiterPoint && (idx0 >= 0) && (idx1 >= 0)) {
                        firstPt[idx0] = miterPt[0];
                        firstPt[idx1] = miterPt[1];
                    }

                    // 1st triangle
                    *idxs++  = vCount+0;
                    *idxs++  = vCount+2;
                    *idxs++  = vCount+1;
                    // 2nd triangle
                    *idxs++  = vCount+1;
                    *idxs++  = vCount+2;
                    *idxs++  = vCount+3;

                    vCount += 4;
                    iCount += 6;
                }
            }
                break;
            case SkPath::kQuad_Verb:
            case SkPath::kCubic_Verb:
                SkDEBUGFAIL("Curves not supported!");
            default:
                // Unhandled cases
                SkASSERT(false);
        }
    }

    if (isOpen) {
        // Add caps
        switch (stroke.getCap()) {
            case SkPaint::kSquare_Cap:
                firstPt[0] -= firstDir;
                firstPt[1] -= firstDir;
                lastPt [0] += dir;
                lastPt [1] += dir;
                break;
            case SkPaint::kRound_Cap:
                SkDEBUGFAIL("Round caps not supported!");
            default: // No cap
                break;
        }
    }

    SkASSERT(vCount <= maxVertexCount);
    SkASSERT(iCount <= maxIndexCount);

    if (vCount > 0) {
        target->drawIndexed(kTriangles_GrPrimitiveType,
                            0,        // start vertex
                            0,        // start index
                            vCount,
                            iCount);
    }

    return true;
}