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,
diff --git a/src/gpu/GrDrawState.h b/src/gpu/GrDrawState.h
index e5c30b6..270912e 100644
--- a/src/gpu/GrDrawState.h
+++ b/src/gpu/GrDrawState.h
@@ -539,6 +539,9 @@
      * When specifying edges as vertex data this enum specifies what type of
      * edges are in use. The edges are always 4 GrScalars in memory, even when
      * the edge type requires fewer than 4.
+     *
+     * TODO: Fix the fact that HairLine and Circle edge types use y-down coords.
+     *       (either adjust in VS or use origin_upper_left in GLSL)
      */
     enum VertexEdgeType {
         /* 1-pixel wide line
@@ -546,11 +549,15 @@
         kHairLine_EdgeType,
         /* Quadratic specified by u^2-v canonical coords (only 2 
            components used). Coverage based on signed distance with negative
-           being inside, positive outside.*/
+           being inside, positive outside. Edge specified in window space
+           (y-down) */
         kQuad_EdgeType,
         /* Same as above but for hairline quadratics. Uses unsigned distance.
            Coverage is min(0, 1-distance). */
         kHairQuad_EdgeType,
+        /* Circle specified as center_x, center_y, outer_radius, inner_radius
+           all in window space (y-down). */
+        kCircle_EdgeType,
 
         kVertexEdgeTypeCnt
     };
diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp
index fb98b4d..3e1f1d4 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -471,10 +471,12 @@
         segments->fVSAttrs.push_back().set(kVec4f_GrSLType,
             GrGLShaderVar::kAttribute_TypeModifier, EDGE_ATTR_NAME);
         segments->fVSCode.appendf("\t%s = " EDGE_ATTR_NAME ";\n", vsName);
-        if (GrDrawState::kHairLine_EdgeType == fProgramDesc.fVertexEdgeType) {
+        switch (fProgramDesc.fVertexEdgeType) {
+        case GrDrawState::kHairLine_EdgeType:
             segments->fFSCode.appendf("\tfloat edgeAlpha = abs(dot(vec3(gl_FragCoord.xy,1), %s.xyz));\n", fsName);
             segments->fFSCode.append("\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
-        } else if (GrDrawState::kQuad_EdgeType == fProgramDesc.fVertexEdgeType) {
+            break;
+        case GrDrawState::kQuad_EdgeType:
             segments->fFSCode.append("\tfloat edgeAlpha;\n");
             // keep the derivative instructions outside the conditional 
             segments->fFSCode.appendf("\tvec2 duvdx = dFdx(%s.xy);\n", fsName);
@@ -492,8 +494,8 @@
             if (kES2_GrGLBinding == gl.binding()) {
                 segments->fHeader.printf("#extension GL_OES_standard_derivatives: enable\n");
             }
-        } else {
-            GrAssert(GrDrawState::kHairQuad_EdgeType == fProgramDesc.fVertexEdgeType);
+            break;
+        case GrDrawState::kHairQuad_EdgeType:
             segments->fFSCode.appendf("\tvec2 duvdx = dFdx(%s.xy);\n", fsName);
             segments->fFSCode.appendf("\tvec2 duvdy = dFdy(%s.xy);\n", fsName);
             segments->fFSCode.appendf("\tvec2 gF = vec2(2.0*%s.x*duvdx.x - duvdx.y,\n"
@@ -505,6 +507,17 @@
             if (kES2_GrGLBinding == gl.binding()) {
                 segments->fHeader.printf("#extension GL_OES_standard_derivatives: enable\n");
             }
+            break;
+        case GrDrawState::kCircle_EdgeType:
+            segments->fFSCode.append("\tfloat edgeAlpha;\n");
+            segments->fFSCode.appendf("\tfloat d = distance(gl_FragCoord.xy, %s.xy);\n", fsName);
+            segments->fFSCode.appendf("\tfloat outerAlpha = smoothstep(d - 0.5, d + 0.5, %s.z);\n", fsName);
+            segments->fFSCode.appendf("\tfloat innerAlpha = %s.w == 0.0 ? 1.0 : smoothstep(%s.w - 0.5, %s.w + 0.5, d);\n", fsName, fsName, fsName);
+            segments->fFSCode.append("\tedgeAlpha = outerAlpha * innerAlpha;\n");
+            break;
+        default:
+            GrCrash("Unknown Edge Type!");
+            break;
         }
         *coverageVar = "edgeAlpha";
     } else {