draw circle paths directly via GPU

Review URL: http://codereview.appspot.com/5696086/

Submitted on behalf of Guanqun.Lu@gmail.com




git-svn-id: http://skia.googlecode.com/svn/trunk@3772 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 8e45e15..1abf682 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -1435,6 +1435,123 @@
     }
 }
 
+struct CircleVertex {
+    GrPoint fPos;
+    GrPoint fCenter;
+    GrScalar fOuterRadius;
+    GrScalar fInnerRadius;
+};
+
+/* Returns true if will map a circle to another circle. This can be true
+ * if the matrix only includes square-scale, rotation, translation.
+ */
+inline bool isSimilarityTransformation(const SkMatrix& matrix,
+                                       SkScalar tol = SK_ScalarNearlyZero) {
+    if (matrix.isIdentity() || matrix.getType() == SkMatrix::kTranslate_Mask) {
+        return true;
+    }
+    if (matrix.hasPerspective()) {
+        return false;
+    }
+
+    SkScalar mx = matrix.get(SkMatrix::kMScaleX);
+    SkScalar sx = matrix.get(SkMatrix::kMSkewX);
+    SkScalar my = matrix.get(SkMatrix::kMScaleY);
+    SkScalar sy = matrix.get(SkMatrix::kMSkewY);
+
+    if (mx == 0 && sx == 0 && my == 0 && sy == 0) {
+        return false;
+    }
+
+    // it has scales or skews, but it could also be rotation, check it out.
+    SkVector vec[2];
+    vec[0].set(mx, sx);
+    vec[1].set(sy, my);
+
+    return SkScalarNearlyZero(vec[0].dot(vec[1]), SkScalarSquare(tol)) &&
+           SkScalarNearlyEqual(vec[0].lengthSqd(), vec[1].lengthSqd(),
+                SkScalarSquare(tol));
+}
+
+}
+
+// TODO: strokeWidth can't be larger than zero right now.
+// It will be fixed when drawPath() can handle strokes.
+void GrContext::drawOval(const GrPaint& paint,
+                         const GrRect& rect,
+                         SkScalar strokeWidth) {
+    DrawCategory category = (DEFER_PATHS) ? kBuffered_DrawCategory :
+                                            kUnbuffered_DrawCategory;
+    GrDrawTarget* target = this->prepareToDraw(paint, category);
+    GrDrawState* drawState = target->drawState();
+    GrMatrix vm = drawState->getViewMatrix();
+
+    if (!isSimilarityTransformation(vm) ||
+        !paint.fAntiAlias ||
+        rect.height() != rect.width()) {
+        SkPath path;
+        path.addOval(rect);
+        GrPathFill fill = (strokeWidth == 0) ?
+                            kHairLine_PathFill : kWinding_PathFill;
+        this->internalDrawPath(paint, path, fill, NULL);
+        return;
+    }
+
+    const GrRenderTarget* rt = drawState->getRenderTarget();
+    if (NULL == rt) {
+        return;
+    }
+
+    GrDrawTarget::AutoDeviceCoordDraw adcd(target, paint.getActiveStageMask());
+
+    GrVertexLayout layout = PaintStageVertexLayoutBits(paint, NULL);
+    layout |= GrDrawTarget::kEdge_VertexLayoutBit;
+    GrAssert(sizeof(CircleVertex) == GrDrawTarget::VertexSize(layout));
+
+    GrPoint center = GrPoint::Make(rect.centerX(), rect.centerY());
+    GrScalar radius = SkScalarHalf(rect.width());
+
+    vm.mapPoints(&center, 1);
+    radius = vm.mapRadius(radius);
+
+    GrScalar outerRadius = radius;
+    GrScalar innerRadius = 0;
+    SkScalar halfWidth = 0;
+    if (strokeWidth == 0) {
+        halfWidth = SkScalarHalf(SK_Scalar1);
+
+        outerRadius += halfWidth;
+        innerRadius = SkMaxScalar(0, radius - halfWidth);
+    }
+
+    GrDrawTarget::AutoReleaseGeometry geo(target, layout, 4, 0);
+    if (!geo.succeeded()) {
+        GrPrintf("Failed to get space for vertices!\n");
+        return;
+    }
+
+    CircleVertex* verts = reinterpret_cast<CircleVertex*>(geo.vertices());
+
+    SkScalar L = center.fX - outerRadius;
+    SkScalar R = center.fX + outerRadius;
+    SkScalar T = center.fY - outerRadius;
+    SkScalar B = center.fY + outerRadius;
+
+    verts[0].fPos = SkPoint::Make(L, T);
+    verts[1].fPos = SkPoint::Make(R, T);
+    verts[2].fPos = SkPoint::Make(L, B);
+    verts[3].fPos = SkPoint::Make(R, B);
+
+    for (int i = 0; i < 4; ++i) {
+        // this goes to fragment shader, it should be in y-points-up space.
+        verts[i].fCenter = SkPoint::Make(center.fX, rt->height() - center.fY);
+
+        verts[i].fOuterRadius = outerRadius;
+        verts[i].fInnerRadius = innerRadius;
+    }
+
+    drawState->setVertexEdgeType(GrDrawState::kCircle_EdgeType);
+    target->drawNonIndexed(kTriangleStrip_PrimitiveType, 0, 4);
 }
 
 
@@ -1499,6 +1616,22 @@
        return;
     }
 
+    SkRect ovalRect;
+    if (!GrIsFillInverted(fill) && path.isOval(&ovalRect)) {
+        if (translate) {
+            ovalRect.offset(*translate);
+        }
+        SkScalar width = (fill == kHairLine_PathFill) ? 0 : -1;
+        this->drawOval(paint, ovalRect, width);
+        return;
+    }
+
+    internalDrawPath(paint, path, fill, translate);
+}
+
+void GrContext::internalDrawPath(const GrPaint& paint, const GrPath& path,
+                                 GrPathFill fill, const GrPoint* translate) {
+
     // Note that below we may sw-rasterize the path into a scratch texture.
     // Scratch textures can be recycled after they are returned to the texture
     // cache. This presents a potential hazard for buffered drawing. However,