Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 780c1287 authored by Chris Craik's avatar Chris Craik
Browse files

HW Acceleration support for stroked arcs with BUTT caps

bug:4419017

Change-Id: I7371bfb36cef460da861a47d4d945218c6d0c3d0
parent a30d9694
Loading
Loading
Loading
Loading
+29 −7
Original line number Original line Diff line number Diff line
@@ -2427,17 +2427,39 @@ status_t OpenGLRenderer::drawOval(float left, float top, float right, float bott
}
}


status_t OpenGLRenderer::drawArc(float left, float top, float right, float bottom,
status_t OpenGLRenderer::drawArc(float left, float top, float right, float bottom,
        float startAngle, float sweepAngle, bool useCenter, SkPaint* paint) {
        float startAngle, float sweepAngle, bool useCenter, SkPaint* p) {
    if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone;
    if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p)) {
        return DrawGlInfo::kStatusDone;
    }


    if (fabs(sweepAngle) >= 360.0f) {
    if (fabs(sweepAngle) >= 360.0f) {
        return drawOval(left, top, right, bottom, paint);
        return drawOval(left, top, right, bottom, p);
    }
    }


    // TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180)
    if (p->getStyle() != SkPaint::kStroke_Style || p->getPathEffect() != 0 || p->getStrokeCap() != SkPaint::kButt_Cap) {
        mCaches.activeTexture(0);
        mCaches.activeTexture(0);
        const PathTexture* texture = mCaches.arcShapeCache.getArc(right - left, bottom - top,
        const PathTexture* texture = mCaches.arcShapeCache.getArc(right - left, bottom - top,
            startAngle, sweepAngle, useCenter, paint);
                startAngle, sweepAngle, useCenter, p);
    return drawShape(left, top, texture, paint);
        return drawShape(left, top, texture, p);
    }

    SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
    if (p->getStyle() == SkPaint::kStrokeAndFill_Style) {
        rect.outset(p->getStrokeWidth() / 2, p->getStrokeWidth() / 2);
    }

    SkPath path;
    if (useCenter) {
        path.moveTo(rect.centerX(), rect.centerY());
    }
    path.arcTo(rect, startAngle, sweepAngle, !useCenter);
    if (useCenter) {
        path.close();
    }
    drawConvexPath(path, p);

    return DrawGlInfo::kStatusDrew;
}
}


// See SkPaintDefaults.h
// See SkPaintDefaults.h
+269 −36
Original line number Original line Diff line number Diff line
@@ -80,11 +80,24 @@ inline void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr) {
 *
 *
 * Note that we can't add and normalize the two vectors, that would result in a rectangle having an
 * Note that we can't add and normalize the two vectors, that would result in a rectangle having an
 * offset of (sqrt(2)/2, sqrt(2)/2) at each corner, instead of (1, 1)
 * offset of (sqrt(2)/2, sqrt(2)/2) at each corner, instead of (1, 1)
 *
 * NOTE: assumes angles between normals 90 degrees or less
 */
 */
inline vec2 totalOffsetFromNormals(const vec2& normalA, const vec2& normalB) {
inline vec2 totalOffsetFromNormals(const vec2& normalA, const vec2& normalB) {
    return (normalA + normalB) / (1 + fabs(normalA.dot(normalB)));
    return (normalA + normalB) / (1 + fabs(normalA.dot(normalB)));
}
}


inline void scaleOffsetForStrokeWidth(vec2& offset, float halfStrokeWidth,
        float inverseScaleX, float inverseScaleY) {
    if (halfStrokeWidth == 0.0f) {
        // hairline - compensate for scale
        offset.x *= 0.5f * inverseScaleX;
        offset.y *= 0.5f * inverseScaleY;
    } else {
        offset *= halfStrokeWidth;
    }
}

void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) {
void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) {
    Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size());
    Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size());


@@ -119,13 +132,7 @@ void getStrokeVerticesFromPerimeter(const Vector<Vertex>& perimeter, float halfS
        nextNormal.normalize();
        nextNormal.normalize();


        vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
        vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
        if (halfStrokeWidth == 0.0f) {
        scaleOffsetForStrokeWidth(totalOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
            // hairline - compensate for scale
            totalOffset.x *= 0.5f * inverseScaleX;
            totalOffset.y *= 0.5f * inverseScaleY;
        } else {
            totalOffset *= halfStrokeWidth;
        }


        Vertex::set(&buffer[currentIndex++],
        Vertex::set(&buffer[currentIndex++],
                current->position[0] + totalOffset.x,
                current->position[0] + totalOffset.x,
@@ -145,6 +152,55 @@ void getStrokeVerticesFromPerimeter(const Vector<Vertex>& perimeter, float halfS
    copyVertex(&buffer[currentIndex++], &buffer[1]);
    copyVertex(&buffer[currentIndex++], &buffer[1]);
}
}


void getStrokeVerticesFromUnclosedVertices(const Vector<Vertex>& vertices, float halfStrokeWidth,
        VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) {
    Vertex* buffer = vertexBuffer.alloc<Vertex>(vertices.size() * 2);

    int currentIndex = 0;
    const Vertex* current = &(vertices[0]);
    vec2 lastNormal;
    for (unsigned int i = 0; i < vertices.size() - 1; i++) {
        const Vertex* next = &(vertices[i + 1]);
        vec2 nextNormal(next->position[1] - current->position[1],
                current->position[0] - next->position[0]);
        nextNormal.normalize();

        vec2 totalOffset;
        if (i == 0) {
            totalOffset = nextNormal;
        } else {
            totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
        }
        scaleOffsetForStrokeWidth(totalOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);

        Vertex::set(&buffer[currentIndex++],
                current->position[0] + totalOffset.x,
                current->position[1] + totalOffset.y);

        Vertex::set(&buffer[currentIndex++],
                current->position[0] - totalOffset.x,
                current->position[1] - totalOffset.y);

        current = next;
        lastNormal = nextNormal;
    }

    vec2 totalOffset = lastNormal;
    scaleOffsetForStrokeWidth(totalOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);

    Vertex::set(&buffer[currentIndex++],
            current->position[0] + totalOffset.x,
            current->position[1] + totalOffset.y);
    Vertex::set(&buffer[currentIndex++],
            current->position[0] - totalOffset.x,
            current->position[1] - totalOffset.y);
#if VERTEX_DEBUG
    for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
        ALOGD("point at %f %f", buffer[i].position[0], buffer[i].position[1]);
    }
#endif
}

void getFillVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer,
void getFillVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer,
         float inverseScaleX, float inverseScaleY) {
         float inverseScaleX, float inverseScaleY) {
    AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(perimeter.size() * 3 + 2);
    AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(perimeter.size() * 3 + 2);
@@ -202,11 +258,167 @@ void getFillVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, VertexBuffe


#if VERTEX_DEBUG
#if VERTEX_DEBUG
    for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
    for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
        ALOGD("point at %f %f", buffer[i].position[0], buffer[i].position[1]);
        ALOGD("point at %f %f, alpha %f", buffer[i].position[0], buffer[i].position[1], buffer[i].alpha);
    }
#endif
}


void getStrokeVerticesFromUnclosedVerticesAA(const Vector<Vertex>& vertices, float halfStrokeWidth,
        VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) {
    AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(6 * vertices.size() + 2);

    // avoid lines smaller than hairline since they break triangle based sampling. instead reducing
    // alpha value (TODO: support different X/Y scale)
    float maxAlpha = 1.0f;
    if (halfStrokeWidth != 0 && inverseScaleX == inverseScaleY &&
            halfStrokeWidth * inverseScaleX < 0.5f) {
        maxAlpha *= (2 * halfStrokeWidth) / inverseScaleX;
        halfStrokeWidth = 0.0f;
    }

    // there is no outer/inner here, using them for consistency with below approach
    int offset = 2 * (vertices.size() - 2);
    int currentAAOuterIndex = 2;
    int currentAAInnerIndex = 2 * offset + 5; // reversed
    int currentStrokeIndex = currentAAInnerIndex + 7;

    const Vertex* last = &(vertices[0]);
    const Vertex* current = &(vertices[1]);
    vec2 lastNormal(current->position[1] - last->position[1],
            last->position[0] - current->position[0]);
    lastNormal.normalize();

    {
        // start cap
        vec2 totalOffset = lastNormal;
        vec2 AAOffset = totalOffset;
        AAOffset.x *= 0.5f * inverseScaleX;
        AAOffset.y *= 0.5f * inverseScaleY;

        vec2 innerOffset = totalOffset;
        scaleOffsetForStrokeWidth(innerOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
        vec2 outerOffset = innerOffset + AAOffset;
        innerOffset -= AAOffset;

        // TODO: support square cap by changing this offset to incorporate halfStrokeWidth
        vec2 capAAOffset(AAOffset.y, -AAOffset.x);
        AlphaVertex::set(&buffer[0],
                last->position[0] + outerOffset.x + capAAOffset.x,
                last->position[1] + outerOffset.y + capAAOffset.y,
                0.0f);
        AlphaVertex::set(&buffer[1],
                last->position[0] + innerOffset.x - capAAOffset.x,
                last->position[1] + innerOffset.y - capAAOffset.y,
                maxAlpha);

        AlphaVertex::set(&buffer[2 * offset + 6],
                last->position[0] - outerOffset.x + capAAOffset.x,
                last->position[1] - outerOffset.y + capAAOffset.y,
                0.0f);
        AlphaVertex::set(&buffer[2 * offset + 7],
                last->position[0] - innerOffset.x - capAAOffset.x,
                last->position[1] - innerOffset.y - capAAOffset.y,
                maxAlpha);
        copyAlphaVertex(&buffer[2 * offset + 8], &buffer[0]);
        copyAlphaVertex(&buffer[2 * offset + 9], &buffer[1]);
        copyAlphaVertex(&buffer[2 * offset + 10], &buffer[1]); // degenerate tris (the only two!)
        copyAlphaVertex(&buffer[2 * offset + 11], &buffer[2 * offset + 7]);
    }

    for (unsigned int i = 1; i < vertices.size() - 1; i++) {
        const Vertex* next = &(vertices[i + 1]);
        vec2 nextNormal(next->position[1] - current->position[1],
                current->position[0] - next->position[0]);
        nextNormal.normalize();

        vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
        vec2 AAOffset = totalOffset;
        AAOffset.x *= 0.5f * inverseScaleX;
        AAOffset.y *= 0.5f * inverseScaleY;

        vec2 innerOffset = totalOffset;
        scaleOffsetForStrokeWidth(innerOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
        vec2 outerOffset = innerOffset + AAOffset;
        innerOffset -= AAOffset;

        AlphaVertex::set(&buffer[currentAAOuterIndex++],
                current->position[0] + outerOffset.x,
                current->position[1] + outerOffset.y,
                0.0f);
        AlphaVertex::set(&buffer[currentAAOuterIndex++],
                current->position[0] + innerOffset.x,
                current->position[1] + innerOffset.y,
                maxAlpha);

        AlphaVertex::set(&buffer[currentStrokeIndex++],
                current->position[0] + innerOffset.x,
                current->position[1] + innerOffset.y,
                maxAlpha);
        AlphaVertex::set(&buffer[currentStrokeIndex++],
                current->position[0] - innerOffset.x,
                current->position[1] - innerOffset.y,
                maxAlpha);

        AlphaVertex::set(&buffer[currentAAInnerIndex--],
                current->position[0] - innerOffset.x,
                current->position[1] - innerOffset.y,
                maxAlpha);
        AlphaVertex::set(&buffer[currentAAInnerIndex--],
                current->position[0] - outerOffset.x,
                current->position[1] - outerOffset.y,
                0.0f);

        last = current;
        current = next;
        lastNormal = nextNormal;
    }

    {
        // end cap
        vec2 totalOffset = lastNormal;
        vec2 AAOffset = totalOffset;
        AAOffset.x *= 0.5f * inverseScaleX;
        AAOffset.y *= 0.5f * inverseScaleY;

        vec2 innerOffset = totalOffset;
        scaleOffsetForStrokeWidth(innerOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
        vec2 outerOffset = innerOffset + AAOffset;
        innerOffset -= AAOffset;

        // TODO: support square cap by changing this offset to incorporate halfStrokeWidth
        vec2 capAAOffset(-AAOffset.y, AAOffset.x);

        AlphaVertex::set(&buffer[offset + 2],
                current->position[0] + outerOffset.x + capAAOffset.x,
                current->position[1] + outerOffset.y + capAAOffset.y,
                0.0f);
        AlphaVertex::set(&buffer[offset + 3],
                current->position[0] + innerOffset.x - capAAOffset.x,
                current->position[1] + innerOffset.y - capAAOffset.y,
                maxAlpha);

        AlphaVertex::set(&buffer[offset + 4],
                current->position[0] - outerOffset.x + capAAOffset.x,
                current->position[1] - outerOffset.y + capAAOffset.y,
                0.0f);
        AlphaVertex::set(&buffer[offset + 5],
                current->position[0] - innerOffset.x - capAAOffset.x,
                current->position[1] - innerOffset.y - capAAOffset.y,
                maxAlpha);

        copyAlphaVertex(&buffer[vertexBuffer.getSize() - 2], &buffer[offset + 3]);
        copyAlphaVertex(&buffer[vertexBuffer.getSize() - 1], &buffer[offset + 5]);
    }

#if VERTEX_DEBUG
    for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
        ALOGD("point at %f %f, alpha %f", buffer[i].position[0], buffer[i].position[1], buffer[i].alpha);
    }
    }
#endif
#endif
}
}



void getStrokeVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, float halfStrokeWidth,
void getStrokeVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, float halfStrokeWidth,
        VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) {
        VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) {
    AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(6 * perimeter.size() + 8);
    AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(6 * perimeter.size() + 8);
@@ -242,13 +454,7 @@ void getStrokeVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, float hal
        AAOffset.y *= 0.5f * inverseScaleY;
        AAOffset.y *= 0.5f * inverseScaleY;


        vec2 innerOffset = totalOffset;
        vec2 innerOffset = totalOffset;
        if (halfStrokeWidth == 0.0f) {
        scaleOffsetForStrokeWidth(innerOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
            // hairline! - compensate for scale
            innerOffset.x *= 0.5f * inverseScaleX;
            innerOffset.y *= 0.5f * inverseScaleY;
        } else {
            innerOffset *= halfStrokeWidth;
        }
        vec2 outerOffset = innerOffset + AAOffset;
        vec2 outerOffset = innerOffset + AAOffset;
        innerOffset -= AAOffset;
        innerOffset -= AAOffset;


@@ -296,6 +502,12 @@ void getStrokeVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, float hal
    copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset]);
    copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset]);
    copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset + 1]);
    copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset + 1]);
    // don't need to create last degenerate tri
    // don't need to create last degenerate tri

#if VERTEX_DEBUG
    for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
        ALOGD("point at %f %f, alpha %f", buffer[i].position[0], buffer[i].position[1], buffer[i].alpha);
    }
#endif
}
}


void PathRenderer::convexPathVertices(const SkPath &path, const SkPaint* paint,
void PathRenderer::convexPathVertices(const SkPath &path, const SkPaint* paint,
@@ -320,7 +532,10 @@ void PathRenderer::convexPathVertices(const SkPath &path, const SkPaint* paint,
            threshInvScaleY *= bounds.height() / (bounds.height() + paint->getStrokeWidth());
            threshInvScaleY *= bounds.height() / (bounds.height() + paint->getStrokeWidth());
        }
        }
    }
    }
    convexPathPerimeterVertices(path, threshInvScaleX * threshInvScaleX,

    // force close if we're filling the path, since fill path expects closed perimeter.
    bool forceClose = style != SkPaint::kStroke_Style;
    bool wasClosed = convexPathPerimeterVertices(path, forceClose, threshInvScaleX * threshInvScaleX,
            threshInvScaleY * threshInvScaleY, tempVertices);
            threshInvScaleY * threshInvScaleY, tempVertices);


    if (!tempVertices.size()) {
    if (!tempVertices.size()) {
@@ -337,11 +552,22 @@ void PathRenderer::convexPathVertices(const SkPath &path, const SkPaint* paint,
    if (style == SkPaint::kStroke_Style) {
    if (style == SkPaint::kStroke_Style) {
        float halfStrokeWidth = paint->getStrokeWidth() * 0.5f;
        float halfStrokeWidth = paint->getStrokeWidth() * 0.5f;
        if (!isAA) {
        if (!isAA) {
            if (wasClosed) {
                getStrokeVerticesFromPerimeter(tempVertices, halfStrokeWidth, vertexBuffer,
                getStrokeVerticesFromPerimeter(tempVertices, halfStrokeWidth, vertexBuffer,
                        inverseScaleX, inverseScaleY);
                        inverseScaleX, inverseScaleY);
            } else {
            } else {
                getStrokeVerticesFromUnclosedVertices(tempVertices, halfStrokeWidth, vertexBuffer,
                        inverseScaleX, inverseScaleY);
            }

        } else {
            if (wasClosed) {
                getStrokeVerticesFromPerimeterAA(tempVertices, halfStrokeWidth, vertexBuffer,
                getStrokeVerticesFromPerimeterAA(tempVertices, halfStrokeWidth, vertexBuffer,
                        inverseScaleX, inverseScaleY);
                        inverseScaleX, inverseScaleY);
            } else {
                getStrokeVerticesFromUnclosedVerticesAA(tempVertices, halfStrokeWidth, vertexBuffer,
                        inverseScaleX, inverseScaleY);
            }
        }
        }
    } else {
    } else {
        // For kStrokeAndFill style, the path should be adjusted externally, as it will be treated as a fill here.
        // For kStrokeAndFill style, the path should be adjusted externally, as it will be treated as a fill here.
@@ -354,19 +580,27 @@ void PathRenderer::convexPathVertices(const SkPath &path, const SkPaint* paint,
}
}




void PathRenderer::convexPathPerimeterVertices(const SkPath& path,
void pushToVector(Vector<Vertex>& vertices, float x, float y) {
    // TODO: make this not yuck
    vertices.push();
    Vertex* newVertex = &(vertices.editArray()[vertices.size() - 1]);
    Vertex::set(newVertex, x, y);
}

bool PathRenderer::convexPathPerimeterVertices(const SkPath& path, bool forceClose,
        float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
        float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
    ATRACE_CALL();
    ATRACE_CALL();


    SkPath::Iter iter(path, true);
    // TODO: to support joins other than sharp miter, join vertices should be labelled in the
    SkPoint pos;
    // perimeter, or resolved into more vertices. Reconsider forceClose-ing in that case.
    SkPath::Iter iter(path, forceClose);
    SkPoint pts[4];
    SkPoint pts[4];
    SkPath::Verb v;
    SkPath::Verb v;
    Vertex* newVertex = 0;
    Vertex* newVertex = 0;
    while (SkPath::kDone_Verb != (v = iter.next(pts))) {
    while (SkPath::kDone_Verb != (v = iter.next(pts))) {
            switch (v) {
            switch (v) {
                case SkPath::kMove_Verb:
                case SkPath::kMove_Verb:
                    pos = pts[0];
                    pushToVector(outputVertices, pts[0].x(), pts[0].y());
                    ALOGV("Move to pos %f %f", pts[0].x(), pts[0].y());
                    ALOGV("Move to pos %f %f", pts[0].x(), pts[0].y());
                    break;
                    break;
                case SkPath::kClose_Verb:
                case SkPath::kClose_Verb:
@@ -377,10 +611,7 @@ void PathRenderer::convexPathPerimeterVertices(const SkPath& path,
                            pts[0].x(), pts[0].y(),
                            pts[0].x(), pts[0].y(),
                            pts[1].x(), pts[1].y());
                            pts[1].x(), pts[1].y());


                    // TODO: make this not yuck
                    pushToVector(outputVertices, pts[1].x(), pts[1].y());
                    outputVertices.push();
                    newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]);
                    Vertex::set(newVertex, pts[1].x(), pts[1].y());
                    break;
                    break;
                case SkPath::kQuad_Verb:
                case SkPath::kQuad_Verb:
                    ALOGV("kQuad_Verb");
                    ALOGV("kQuad_Verb");
@@ -403,6 +634,14 @@ void PathRenderer::convexPathPerimeterVertices(const SkPath& path,
                    break;
                    break;
            }
            }
    }
    }

    int size = outputVertices.size();
    if (size >= 2 && outputVertices[0].position[0] == outputVertices[size - 1].position[0] &&
            outputVertices[0].position[1] == outputVertices[size - 1].position[1]) {
        outputVertices.pop();
        return true;
    }
    return false;
}
}


void PathRenderer::recursiveCubicBezierVertices(
void PathRenderer::recursiveCubicBezierVertices(
@@ -419,10 +658,7 @@ void PathRenderer::recursiveCubicBezierVertices(


    if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
    if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
        // below thresh, draw line by adding endpoint
        // below thresh, draw line by adding endpoint
        // TODO: make this not yuck
        pushToVector(outputVertices, p2x, p2y);
        outputVertices.push();
        Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]);
        Vertex::set(newVertex, p2x, p2y);
    } else {
    } else {
        float p1c1x = (p1x + c1x) * 0.5f;
        float p1c1x = (p1x + c1x) * 0.5f;
        float p1c1y = (p1y + c1y) * 0.5f;
        float p1c1y = (p1y + c1y) * 0.5f;
@@ -463,10 +699,7 @@ void PathRenderer::recursiveQuadraticBezierVertices(


    if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
    if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
        // below thresh, draw line by adding endpoint
        // below thresh, draw line by adding endpoint
        // TODO: make this not yuck
        pushToVector(outputVertices, bx, by);
        outputVertices.push();
        Vertex* newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]);
        Vertex::set(newVertex, bx, by);
    } else {
    } else {
        float acx = (ax + cx) * 0.5f;
        float acx = (ax + cx) * 0.5f;
        float bcx = (bx + cx) * 0.5f;
        float bcx = (bx + cx) * 0.5f;
+2 −4
Original line number Original line Diff line number Diff line
@@ -71,10 +71,8 @@ public:
            const mat4 *transform, VertexBuffer& vertexBuffer);
            const mat4 *transform, VertexBuffer& vertexBuffer);


private:
private:
    static void convexPathPerimeterVertices(
    static bool convexPathPerimeterVertices(const SkPath &path, bool forceClose,
        const SkPath &path,
        float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex> &outputVertices);
        float sqrInvScaleX, float sqrInvScaleY,
        Vector<Vertex> &outputVertices);


/*
/*
  endpoints a & b,
  endpoints a & b,