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

Commit 2aa50b6b authored by Chris Craik's avatar Chris Craik Committed by Android (Google) Code Review
Browse files

Merge "HW Acceleration support for stroked arcs with BUTT caps" into jb-mr1-dev

parents 3f840c8c 780c1287
Loading
Loading
Loading
Loading
+29 −7
Original line number 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,
        float startAngle, float sweepAngle, bool useCenter, SkPaint* paint) {
    if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone;
        float startAngle, float sweepAngle, bool useCenter, SkPaint* p) {
    if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p)) {
        return DrawGlInfo::kStatusDone;
    }

    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);
        const PathTexture* texture = mCaches.arcShapeCache.getArc(right - left, bottom - top,
            startAngle, sweepAngle, useCenter, paint);
    return drawShape(left, top, texture, paint);
                startAngle, sweepAngle, useCenter, p);
        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
+269 −36
Original line number 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
 * 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) {
    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) {
    Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size());

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

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

        Vertex::set(&buffer[currentIndex++],
                current->position[0] + totalOffset.x,
@@ -145,6 +152,55 @@ void getStrokeVerticesFromPerimeter(const Vector<Vertex>& perimeter, float halfS
    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,
         float inverseScaleX, float inverseScaleY) {
    AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(perimeter.size() * 3 + 2);
@@ -202,11 +258,167 @@ void getFillVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, VertexBuffe

#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]);
        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
}


void getStrokeVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, float halfStrokeWidth,
        VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) {
    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;

        vec2 innerOffset = totalOffset;
        if (halfStrokeWidth == 0.0f) {
            // hairline! - compensate for scale
            innerOffset.x *= 0.5f * inverseScaleX;
            innerOffset.y *= 0.5f * inverseScaleY;
        } else {
            innerOffset *= halfStrokeWidth;
        }
        scaleOffsetForStrokeWidth(innerOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
        vec2 outerOffset = 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 + 1]);
    // 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,
@@ -320,7 +532,10 @@ void PathRenderer::convexPathVertices(const SkPath &path, const SkPaint* paint,
            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);

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

        } else {
            if (wasClosed) {
                getStrokeVerticesFromPerimeterAA(tempVertices, halfStrokeWidth, vertexBuffer,
                        inverseScaleX, inverseScaleY);
            } else {
                getStrokeVerticesFromUnclosedVerticesAA(tempVertices, halfStrokeWidth, vertexBuffer,
                        inverseScaleX, inverseScaleY);
            }
        }
    } else {
        // 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) {
    ATRACE_CALL();

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

                    // TODO: make this not yuck
                    outputVertices.push();
                    newVertex = &(outputVertices.editArray()[outputVertices.size() - 1]);
                    Vertex::set(newVertex, pts[1].x(), pts[1].y());
                    pushToVector(outputVertices, pts[1].x(), pts[1].y());
                    break;
                case SkPath::kQuad_Verb:
                    ALOGV("kQuad_Verb");
@@ -403,6 +634,14 @@ void PathRenderer::convexPathPerimeterVertices(const SkPath& path,
                    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(
@@ -419,10 +658,7 @@ void PathRenderer::recursiveCubicBezierVertices(

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

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

/*
  endpoints a & b,