Shader optimization for ovals.

Previously we were setting a center point as an attribute 
across all vertices, then using that with the interpolated 
position to compute an offset. However, because the offset 
computation is linear, we can just set the appropriate offset 
at the vertices and let the rasterizer interpolate it for us. 

Author: jvanverth@google.com

Reviewed By: robertphillips@google.com

Review URL: https://chromiumcodereview.appspot.com/14093002

git-svn-id: http://skia.googlecode.com/svn/trunk@8613 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/gpu/GrOvalRenderer.cpp b/src/gpu/GrOvalRenderer.cpp
index 61f27f4..731964c 100644
--- a/src/gpu/GrOvalRenderer.cpp
+++ b/src/gpu/GrOvalRenderer.cpp
@@ -21,19 +21,18 @@
 namespace {
 
 struct CircleVertex {
-    GrPoint fPos;
-    GrPoint fCenter;
+    GrPoint  fPos;
+    GrPoint  fOffset;
     SkScalar fOuterRadius;
     SkScalar fInnerRadius;
 };
 
 struct EllipseVertex {
-    GrPoint fPos;
-    GrPoint fCenter;
+    GrPoint  fPos;
     SkScalar fOuterXRadius;
-    SkScalar fOuterXYRatio;
     SkScalar fInnerXRadius;
-    SkScalar fInnerXYRatio;
+    GrPoint  fOuterOffset;
+    GrPoint  fInnerOffset;
 };
 
 inline bool circle_stays_circle(const SkMatrix& m) {
@@ -46,8 +45,7 @@
 
 /**
  * The output of this effect is a modulation of the input color and coverage for a circle,
- * specified as center_x, center_y, x_radius, inner radius and outer radius in window space
- * (y-down).
+ * specified as offset_x, offset_y (both from center point), outer radius and inner radius.
  */
 
 class CircleEdgeEffect : public GrEffect {
@@ -102,13 +100,13 @@
                 builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]);
             builder->vsCodeAppendf("\t%s = %s;\n", vsName, attrName->c_str());
 
-            builder->fsCodeAppendf("\tfloat d = distance(%s.xy, %s.xy);\n",
-                                   builder->fragmentPosition(), fsName);
+            builder->fsCodeAppendf("\tfloat d = length(%s.xy);\n", fsName);
             builder->fsCodeAppendf("\tfloat edgeAlpha = clamp(%s.z - d, 0.0, 1.0);\n", fsName);
             if (circleEffect.isStroked()) {
                 builder->fsCodeAppendf("\tfloat innerAlpha = clamp(d - %s.w, 0.0, 1.0);\n", fsName);
                 builder->fsCodeAppend("\tedgeAlpha *= innerAlpha;\n");
             }
+
             SkString modulate;
             GrGLSLModulate4f(&modulate, inputColor, "edgeAlpha");
             builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
@@ -158,7 +156,7 @@
 
 /**
  * The output of this effect is a modulation of the input color and coverage for an axis-aligned
- * ellipse, specified as center_x, center_y, x_radius, x_radius/y_radius in window space (y-down).
+ * ellipse, specified as  outer and inner radii, and outer and inner offsets from center.
  */
 
 class EllipseEdgeEffect : public GrEffect {
@@ -207,37 +205,31 @@
                               const TextureSamplerArray& samplers) SK_OVERRIDE {
             const EllipseEdgeEffect& ellipseEffect = drawEffect.castEffect<EllipseEdgeEffect>();
 
-            const char *vsCenterName, *fsCenterName;
-            const char *vsEdgeName, *fsEdgeName;
+            const char *vsRadiiName, *fsRadiiName;
+            const char *vsOffsetsName, *fsOffsetsName;
 
-            builder->addVarying(kVec2f_GrSLType, "EllipseCenter", &vsCenterName, &fsCenterName);
+            builder->addVarying(kVec2f_GrSLType, "EllipseRadii", &vsRadiiName, &fsRadiiName);
             const SkString* attr0Name =
                 builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]);
-            builder->vsCodeAppendf("\t%s = %s;\n", vsCenterName, attr0Name->c_str());
+            builder->vsCodeAppendf("\t%s = %s;\n", vsRadiiName, attr0Name->c_str());
 
-            builder->addVarying(kVec4f_GrSLType, "EllipseEdge", &vsEdgeName, &fsEdgeName);
+            builder->addVarying(kVec4f_GrSLType, "EllipseOffsets", &vsOffsetsName, &fsOffsetsName);
             const SkString* attr1Name =
                 builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[1]);
-            builder->vsCodeAppendf("\t%s = %s;\n", vsEdgeName, attr1Name->c_str());
+            builder->vsCodeAppendf("\t%s = %s;\n", vsOffsetsName, attr1Name->c_str());
 
-            // translate to origin
-            builder->fsCodeAppendf("\tvec2 outerOffset = (%s.xy - %s.xy);\n",
-                                   builder->fragmentPosition(), fsCenterName);
-            builder->fsCodeAppend("\tvec2 innerOffset = outerOffset;\n");
-            // scale y by xRadius/yRadius
-            builder->fsCodeAppendf("\touterOffset.y *= %s.y;\n", fsEdgeName);
-            builder->fsCodeAppend("\tfloat dOuter = length(outerOffset);\n");
+            // get length of offset
+            builder->fsCodeAppendf("\tfloat dOuter = length(%s.xy);\n", fsOffsetsName);
             // compare outer lengths against xOuterRadius
             builder->fsCodeAppendf("\tfloat edgeAlpha = clamp(%s.x-dOuter, 0.0, 1.0);\n",
-                                   fsEdgeName);
+                                   fsRadiiName);
 
             if (ellipseEffect.isStroked()) {
-                builder->fsCodeAppendf("\tinnerOffset.y *= %s.w;\n", fsEdgeName);
-                builder->fsCodeAppend("\tfloat dInner = length(innerOffset);\n");
+                builder->fsCodeAppendf("\tfloat dInner = length(%s.zw);\n", fsOffsetsName);
 
                 // compare inner lengths against xInnerRadius
-                builder->fsCodeAppendf("\tfloat innerAlpha = clamp(dInner-%s.z, 0.0, 1.0);\n",
-                                       fsEdgeName);
+                builder->fsCodeAppendf("\tfloat innerAlpha = clamp(dInner-%s.y, 0.0, 1.0);\n",
+                                       fsRadiiName);
                 builder->fsCodeAppend("\tedgeAlpha *= innerAlpha;\n");
             }
 
@@ -384,12 +376,6 @@
     outerRadius += SK_ScalarHalf;
     innerRadius -= SK_ScalarHalf;
 
-    for (int i = 0; i < 4; ++i) {
-        verts[i].fCenter = center;
-        verts[i].fOuterRadius = outerRadius;
-        verts[i].fInnerRadius = innerRadius;
-    }
-
     SkRect bounds = SkRect::MakeLTRB(
         center.fX - outerRadius,
         center.fY - outerRadius,
@@ -398,9 +384,24 @@
     );
 
     verts[0].fPos = SkPoint::Make(bounds.fLeft,  bounds.fTop);
+    verts[0].fOffset = SkPoint::Make(-outerRadius, -outerRadius);
+    verts[0].fOuterRadius = outerRadius;
+    verts[0].fInnerRadius = innerRadius;
+
     verts[1].fPos = SkPoint::Make(bounds.fRight, bounds.fTop);
+    verts[1].fOffset = SkPoint::Make(outerRadius, -outerRadius);   
+    verts[1].fOuterRadius = outerRadius;
+    verts[1].fInnerRadius = innerRadius;
+
     verts[2].fPos = SkPoint::Make(bounds.fLeft,  bounds.fBottom);
+    verts[2].fOffset = SkPoint::Make(-outerRadius, outerRadius);
+    verts[2].fOuterRadius = outerRadius;
+    verts[2].fInnerRadius = innerRadius;
+
     verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom);
+    verts[3].fOffset = SkPoint::Make(outerRadius, outerRadius);
+    verts[3].fOuterRadius = outerRadius;
+    verts[3].fInnerRadius = innerRadius;
 
     target->drawNonIndexed(kTriangleStrip_GrPrimitiveType, 0, 4, &bounds);
 }
@@ -495,30 +496,46 @@
 
     SkScalar outerRatio = SkScalarDiv(xRadius, yRadius);
 
-    for (int i = 0; i < 4; ++i) {
-        verts[i].fCenter = center;
-        verts[i].fOuterXRadius = xRadius + 0.5f;
-        verts[i].fOuterXYRatio = outerRatio;
-        verts[i].fInnerXRadius = innerXRadius - 0.5f;
-        verts[i].fInnerXYRatio = innerRatio;
-    }
-
-    SkScalar L = -xRadius;
-    SkScalar R = +xRadius;
-    SkScalar T = -yRadius;
-    SkScalar B = +yRadius;
-
     // We've extended the outer x radius out half a pixel to antialias.
-    // Expand the drawn rect here so all the pixels will be captured.
-    L += center.fX - SK_ScalarHalf;
-    R += center.fX + SK_ScalarHalf;
-    T += center.fY - SK_ScalarHalf;
-    B += center.fY + SK_ScalarHalf;
+    // This will also expand the rect so all the pixels will be captured.
+    xRadius += SK_ScalarHalf;
+    yRadius += SK_ScalarHalf;
+    innerXRadius -= SK_ScalarHalf;
 
-    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);
+    SkRect bounds = SkRect::MakeLTRB(
+        center.fX - xRadius,
+        center.fY - yRadius,
+        center.fX + xRadius,
+        center.fY + yRadius
+    );
 
-    target->drawNonIndexed(kTriangleStrip_GrPrimitiveType, 0, 4);
+    // The offsets are created by scaling the y radius by the appropriate ratio. This way we end up 
+    // with a circle equation which can be checked quickly in the shader. We need one offset for 
+    // outer and one for inner because they have different scale factors -- otherwise we end up with 
+    // non-uniform strokes.
+    verts[0].fPos = SkPoint::Make(bounds.fLeft,  bounds.fTop);
+    verts[0].fOuterXRadius = xRadius;
+    verts[0].fInnerXRadius = innerXRadius;
+    verts[0].fOuterOffset = SkPoint::Make(-xRadius, -outerRatio*yRadius);
+    verts[0].fInnerOffset = SkPoint::Make(-xRadius, -innerRatio*yRadius);
+
+    verts[1].fPos = SkPoint::Make(bounds.fRight, bounds.fTop);
+    verts[1].fOuterXRadius = xRadius;
+    verts[1].fInnerXRadius = innerXRadius;
+    verts[1].fOuterOffset = SkPoint::Make(xRadius, -outerRatio*yRadius);    
+    verts[1].fInnerOffset = SkPoint::Make(xRadius, -innerRatio*yRadius);    
+
+    verts[2].fPos = SkPoint::Make(bounds.fLeft,  bounds.fBottom);
+    verts[2].fOuterXRadius = xRadius;
+    verts[2].fInnerXRadius = innerXRadius;
+    verts[2].fOuterOffset = SkPoint::Make(-xRadius, outerRatio*yRadius);
+    verts[2].fInnerOffset = SkPoint::Make(-xRadius, innerRatio*yRadius);
+
+    verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom);
+    verts[3].fOuterXRadius = xRadius;
+    verts[3].fInnerXRadius = innerXRadius;
+    verts[3].fOuterOffset = SkPoint::Make(xRadius, outerRatio*yRadius);
+    verts[3].fInnerOffset = SkPoint::Make(xRadius, innerRatio*yRadius);
+ 
+    target->drawNonIndexed(kTriangleStrip_GrPrimitiveType, 0, 4, &bounds);
 }