Chromium Code Reviews| Index: src/gpu/GrOvalRenderer.cpp | 
| diff --git a/src/gpu/GrOvalRenderer.cpp b/src/gpu/GrOvalRenderer.cpp | 
| index 6965f9e5805eab03995f6bfe2c9ba5eb3bba9ca2..f06225618951c0362a6701b5a31614427577d592 100644 | 
| --- a/src/gpu/GrOvalRenderer.cpp | 
| +++ b/src/gpu/GrOvalRenderer.cpp | 
| @@ -37,6 +37,12 @@ struct EllipseVertex { | 
| GrPoint fInnerRadii; | 
| }; | 
| +struct DIEllipseVertex { | 
| + GrPoint fPos; | 
| + GrPoint fOuterOffset; | 
| + GrPoint fInnerOffset; | 
| +}; | 
| + | 
| inline bool circle_stays_circle(const SkMatrix& m) { | 
| return m.isSimilarity(); | 
| } | 
| @@ -292,6 +298,158 @@ GrEffectRef* EllipseEdgeEffect::TestCreate(SkMWCRandom* random, | 
| /////////////////////////////////////////////////////////////////////////////// | 
| +/** | 
| + * The output of this effect is a modulation of the input color and coverage for an ellipse, | 
| + * specified as a 2D offset from center for both the outer and inner paths (if stroked). The | 
| + * implict equation used is for a unit circle (x^2 + y^2 - 1 = 0) and the edge corrected by | 
| + * using differentials. | 
| + * | 
| + * The result is device-independent and can be used with any affine matrix. | 
| + */ | 
| + | 
| +class DIEllipseEdgeEffect : public GrEffect { | 
| +public: | 
| + enum Mode { kStroke = 0, kHairline, kFill }; | 
| + | 
| + static GrEffectRef* Create(Mode mode) { | 
| + GR_CREATE_STATIC_EFFECT(gEllipseStrokeEdge, DIEllipseEdgeEffect, (kStroke)); | 
| + GR_CREATE_STATIC_EFFECT(gEllipseHairlineEdge, DIEllipseEdgeEffect, (kHairline)); | 
| + GR_CREATE_STATIC_EFFECT(gEllipseFillEdge, DIEllipseEdgeEffect, (kFill)); | 
| + | 
| + if (kStroke == mode) { | 
| + gEllipseStrokeEdge->ref(); | 
| + return gEllipseStrokeEdge; | 
| + } else if (kHairline == mode) { | 
| + gEllipseHairlineEdge->ref(); | 
| + return gEllipseHairlineEdge; | 
| + } else { | 
| + gEllipseFillEdge->ref(); | 
| + return gEllipseFillEdge; | 
| + } | 
| + } | 
| + | 
| + virtual void getConstantColorComponents(GrColor* color, | 
| + uint32_t* validFlags) const SK_OVERRIDE { | 
| + *validFlags = 0; | 
| + } | 
| + | 
| + virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE { | 
| + return GrTBackendEffectFactory<DIEllipseEdgeEffect>::getInstance(); | 
| + } | 
| + | 
| + virtual ~DIEllipseEdgeEffect() {} | 
| + | 
| + static const char* Name() { return "DIEllipseEdge"; } | 
| + | 
| + inline Mode getMode() const { return fMode; } | 
| + | 
| + class GLEffect : public GrGLEffect { | 
| + public: | 
| + GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&) | 
| + : INHERITED (factory) {} | 
| + | 
| + virtual void emitCode(GrGLShaderBuilder* builder, | 
| + const GrDrawEffect& drawEffect, | 
| + EffectKey key, | 
| + const char* outputColor, | 
| + const char* inputColor, | 
| + const TextureSamplerArray& samplers) SK_OVERRIDE { | 
| + GrGLShaderBuilder::VertexBuilder* vertexBuilder = builder->getVertexBuilder(); | 
| + SkASSERT(NULL != vertexBuilder); | 
| + | 
| + const DIEllipseEdgeEffect& ellipseEffect = drawEffect.castEffect<DIEllipseEdgeEffect>(); | 
| + | 
| + SkAssertResult(builder->enableFeature( | 
| + GrGLShaderBuilder::kStandardDerivatives_GLSLFeature)); | 
| + | 
| + const char *vsOffsetName, *fsOffsetName; | 
| + vertexBuilder->addVarying(kVec4f_GrSLType, "EllipseOffsets", | 
| + &vsOffsetName, &fsOffsetName); | 
| + const SkString* attr0Name = | 
| + vertexBuilder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]); | 
| + vertexBuilder->vsCodeAppendf("\t%s = %s;\n", vsOffsetName, attr0Name->c_str()); | 
| + | 
| + // for outer curve | 
| + builder->fsCodeAppendf("\tvec2 scaledOffset = %s.xy;\n", fsOffsetName); | 
| + builder->fsCodeAppend("\tfloat test = dot(scaledOffset, scaledOffset) - 1.0;\n"); | 
| + builder->fsCodeAppendf("\tvec4 duvdx = dFdx(%s);\n", fsOffsetName); | 
| + builder->fsCodeAppendf("\tvec4 duvdy = dFdy(%s);\n", fsOffsetName); | 
| + builder->fsCodeAppendf("\tvec2 grad = vec2(2.0*%s.x*duvdx.x + 2.0*%s.y*duvdx.y,\n" | 
| + "\t 2.0*%s.x*duvdy.x + 2.0*%s.y*duvdy.y);\n", | 
| + fsOffsetName, fsOffsetName, fsOffsetName, fsOffsetName); | 
| + | 
| + builder->fsCodeAppend("\tfloat grad_dot = dot(grad, grad);\n"); | 
| + // we need to clamp the length^2 of the gradiant vector to a non-zero value, because | 
| + // on the Nexus 4 the undefined result of inversesqrt(0) drops out an entire tile | 
| + // TODO: restrict this to Adreno-only | 
| + builder->fsCodeAppend("\tgrad_dot = max(grad_dot, 1.0e-4);\n"); | 
| + builder->fsCodeAppend("\tfloat invlen = inversesqrt(grad_dot);\n"); | 
| + if (kHairline == ellipseEffect.getMode()) { | 
| + // can probably do this with one step | 
| + builder->fsCodeAppend("\tfloat edgeAlpha = clamp(1.0-test*invlen, 0.0, 1.0);\n"); | 
| + builder->fsCodeAppend("\tedgeAlpha *= clamp(1.0+test*invlen, 0.0, 1.0);\n"); | 
| + } else { | 
| + builder->fsCodeAppend("\tfloat edgeAlpha = clamp(0.5-test*invlen, 0.0, 1.0);\n"); | 
| + } | 
| + | 
| + // for inner curve | 
| + if (kStroke == ellipseEffect.getMode()) { | 
| + builder->fsCodeAppendf("\tscaledOffset = %s.zw;\n", fsOffsetName); | 
| + builder->fsCodeAppend("\ttest = dot(scaledOffset, scaledOffset) - 1.0;\n"); | 
| + builder->fsCodeAppendf("\tgrad = vec2(2.0*%s.z*duvdx.z + 2.0*%s.w*duvdx.w,\n" | 
| + "\t 2.0*%s.z*duvdy.z + 2.0*%s.w*duvdy.w);\n", | 
| + fsOffsetName, fsOffsetName, fsOffsetName, fsOffsetName); | 
| + builder->fsCodeAppend("\tinvlen = inversesqrt(dot(grad, grad));\n"); | 
| + builder->fsCodeAppend("\tedgeAlpha *= clamp(0.5+test*invlen, 0.0, 1.0);\n"); | 
| + } | 
| + | 
| + SkString modulate; | 
| + GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha"); | 
| + builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str()); | 
| + } | 
| + | 
| + static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) { | 
| + const DIEllipseEdgeEffect& ellipseEffect = drawEffect.castEffect<DIEllipseEdgeEffect>(); | 
| + | 
| + return ellipseEffect.getMode(); | 
| + } | 
| + | 
| + virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE { | 
| + } | 
| + | 
| + private: | 
| + typedef GrGLEffect INHERITED; | 
| + }; | 
| + | 
| +private: | 
| + DIEllipseEdgeEffect(Mode mode) : GrEffect() { | 
| + this->addVertexAttrib(kVec4f_GrSLType); | 
| + fMode = mode; | 
| + } | 
| + | 
| + virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE { | 
| + const DIEllipseEdgeEffect& eee = CastEffect<DIEllipseEdgeEffect>(other); | 
| + return eee.fMode == fMode; | 
| + } | 
| + | 
| + Mode fMode; | 
| + | 
| + GR_DECLARE_EFFECT_TEST; | 
| + | 
| + typedef GrEffect INHERITED; | 
| +}; | 
| + | 
| +GR_DEFINE_EFFECT_TEST(DIEllipseEdgeEffect); | 
| + | 
| +GrEffectRef* DIEllipseEdgeEffect::TestCreate(SkMWCRandom* random, | 
| + GrContext* context, | 
| + const GrDrawTargetCaps&, | 
| + GrTexture* textures[]) { | 
| + return DIEllipseEdgeEffect::Create((Mode)(random->nextRangeU(0,2))); | 
| +} | 
| + | 
| +/////////////////////////////////////////////////////////////////////////////// | 
| + | 
| void GrOvalRenderer::reset() { | 
| GrSafeSetNull(fRRectIndexBuffer); | 
| } | 
| @@ -309,11 +467,12 @@ bool GrOvalRenderer::drawOval(GrDrawTarget* target, const GrContext* context, bo | 
| if (SkScalarNearlyEqual(oval.width(), oval.height()) | 
| && circle_stays_circle(vm)) { | 
| this->drawCircle(target, useAA, oval, stroke); | 
| - | 
| - // and axis-aligned ellipses only | 
| + // if we have shader derivative support, render as device-independent | 
| + } else if (target->caps()->shaderDerivativeSupport()) { | 
| 
 
bsalomon
2013/09/04 20:51:16
Is it better to prefer the circle renderer or shou
 
 | 
| + return this->drawDIEllipse(target, useAA, oval, stroke); | 
| + // otherwise axis-aligned ellipses only | 
| } else if (vm.rectStaysRect()) { | 
| return this->drawEllipse(target, useAA, oval, stroke); | 
| - | 
| } else { | 
| return false; | 
| } | 
| @@ -321,8 +480,6 @@ bool GrOvalRenderer::drawOval(GrDrawTarget* target, const GrContext* context, bo | 
| return true; | 
| } | 
| -namespace { | 
| - | 
| /////////////////////////////////////////////////////////////////////////////// | 
| // position + edge | 
| @@ -331,8 +488,6 @@ extern const GrVertexAttrib gCircleVertexAttribs[] = { | 
| {kVec4f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding} | 
| }; | 
| -}; | 
| - | 
| void GrOvalRenderer::drawCircle(GrDrawTarget* target, | 
| bool useAA, | 
| const SkRect& circle, | 
| @@ -424,15 +579,17 @@ void GrOvalRenderer::drawCircle(GrDrawTarget* target, | 
| /////////////////////////////////////////////////////////////////////////////// | 
| -namespace { | 
| - | 
| -// position + edge | 
| +// position + offset + 1/radii | 
| extern const GrVertexAttrib gEllipseVertexAttribs[] = { | 
| {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding}, | 
| {kVec2f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding}, | 
| {kVec4f_GrVertexAttribType, 2*sizeof(GrPoint), kEffect_GrVertexAttribBinding} | 
| }; | 
| +// position + offsets | 
| +extern const GrVertexAttrib gDIEllipseVertexAttribs[] = { | 
| + {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding}, | 
| + {kVec4f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding}, | 
| }; | 
| bool GrOvalRenderer::drawEllipse(GrDrawTarget* target, | 
| @@ -469,8 +626,8 @@ bool GrOvalRenderer::drawEllipse(GrDrawTarget* target, | 
| SkStrokeRec::Style style = stroke.getStyle(); | 
| bool isStroked = (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style); | 
| - SkScalar innerXRadius = 0.0f; | 
| - SkScalar innerYRadius = 0.0f; | 
| + SkScalar innerXRadius = 0; | 
| + SkScalar innerYRadius = 0; | 
| if (SkStrokeRec::kFill_Style != style) { | 
| if (SkScalarNearlyZero(scaledStroke.length())) { | 
| scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf); | 
| @@ -520,8 +677,7 @@ bool GrOvalRenderer::drawEllipse(GrDrawTarget* target, | 
| innerXRadius > 0 && innerYRadius > 0); | 
| static const int kEllipseCenterAttrIndex = 1; | 
| - static const int kEllipseEdgeAttrIndex = 2; | 
| - drawState->addCoverageEffect(effect, kEllipseCenterAttrIndex, kEllipseEdgeAttrIndex)->unref(); | 
| + drawState->addCoverageEffect(effect, kEllipseCenterAttrIndex)->unref(); | 
| // Compute the reciprocals of the radii here to save time in the shader | 
| SkScalar xRadRecip = SkScalarInvert(xRadius); | 
| @@ -567,6 +723,120 @@ bool GrOvalRenderer::drawEllipse(GrDrawTarget* target, | 
| return true; | 
| } | 
| +bool GrOvalRenderer::drawDIEllipse(GrDrawTarget* target, | 
| + bool useAA, | 
| + const SkRect& ellipse, | 
| + const SkStrokeRec& stroke) | 
| +{ | 
| + GrDrawState* drawState = target->drawState(); | 
| + const SkMatrix& vm = drawState->getViewMatrix(); | 
| + | 
| + GrPoint center = GrPoint::Make(ellipse.centerX(), ellipse.centerY()); | 
| + SkScalar xRadius = SkScalarHalf(ellipse.width()); | 
| + SkScalar yRadius = SkScalarHalf(ellipse.height()); | 
| + | 
| + SkStrokeRec::Style style = stroke.getStyle(); | 
| + DIEllipseEdgeEffect::Mode mode = (SkStrokeRec::kStroke_Style == style) ? | 
| + DIEllipseEdgeEffect::kStroke : | 
| + (SkStrokeRec::kHairline_Style == style) ? | 
| + DIEllipseEdgeEffect::kHairline : DIEllipseEdgeEffect::kFill; | 
| + | 
| + SkScalar innerXRadius = 0; | 
| + SkScalar innerYRadius = 0; | 
| + if (SkStrokeRec::kFill_Style != style && SkStrokeRec::kHairline_Style != style) { | 
| + SkScalar strokeWidth = stroke.getWidth(); | 
| + | 
| + if (SkScalarNearlyZero(strokeWidth)) { | 
| + strokeWidth = SK_ScalarHalf; | 
| + } else { | 
| + strokeWidth *= SK_ScalarHalf; | 
| + } | 
| + | 
| + // we only handle thick strokes for near-circular ellipses | 
| + if (strokeWidth > SK_ScalarHalf && | 
| + (SK_ScalarHalf*xRadius > yRadius || SK_ScalarHalf*yRadius > xRadius)) { | 
| + return false; | 
| + } | 
| + | 
| + // we don't handle it if curvature of the stroke is less than curvature of the ellipse | 
| + if (strokeWidth*(yRadius*yRadius) < (strokeWidth*strokeWidth)*xRadius || | 
| + strokeWidth*(xRadius*xRadius) < (strokeWidth*strokeWidth)*yRadius) { | 
| + return false; | 
| + } | 
| + | 
| + // set inner radius (if needed) | 
| + if (SkStrokeRec::kStroke_Style == style) { | 
| + innerXRadius = xRadius - strokeWidth; | 
| + innerYRadius = yRadius - strokeWidth; | 
| + } | 
| + | 
| + xRadius += strokeWidth; | 
| + yRadius += strokeWidth; | 
| + } | 
| + if (DIEllipseEdgeEffect::kStroke == mode) { | 
| + mode = (innerXRadius > 0 && innerYRadius > 0) ? DIEllipseEdgeEffect::kStroke : | 
| + DIEllipseEdgeEffect::kFill; | 
| + } | 
| + SkScalar innerRatioX = SkScalarDiv(xRadius, innerXRadius); | 
| + SkScalar innerRatioY = SkScalarDiv(yRadius, innerYRadius); | 
| + | 
| + drawState->setVertexAttribs<gDIEllipseVertexAttribs>(SK_ARRAY_COUNT(gDIEllipseVertexAttribs)); | 
| + SkASSERT(sizeof(DIEllipseVertex) == drawState->getVertexSize()); | 
| + | 
| + GrDrawTarget::AutoReleaseGeometry geo(target, 4, 0); | 
| + if (!geo.succeeded()) { | 
| + GrPrintf("Failed to get space for vertices!\n"); | 
| + return false; | 
| + } | 
| + | 
| + DIEllipseVertex* verts = reinterpret_cast<DIEllipseVertex*>(geo.vertices()); | 
| + | 
| + GrEffectRef* effect = DIEllipseEdgeEffect::Create(mode); | 
| + | 
| + static const int kEllipseOuterOffsetAttrIndex = 1; | 
| + static const int kEllipseInnerOffsetAttrIndex = 2; | 
| + drawState->addCoverageEffect(effect, kEllipseOuterOffsetAttrIndex, | 
| + kEllipseInnerOffsetAttrIndex)->unref(); | 
| + | 
| + // This expands the outer rect so that after CTM we end up with a half-pixel border | 
| + SkScalar a = vm[SkMatrix::kMScaleX]; | 
| + SkScalar b = vm[SkMatrix::kMSkewX]; | 
| + SkScalar c = vm[SkMatrix::kMSkewY]; | 
| + SkScalar d = vm[SkMatrix::kMScaleY]; | 
| + SkScalar geoDx = SkScalarDiv(SK_ScalarHalf, SkScalarSqrt(a*a + c*c)); | 
| + SkScalar geoDy = SkScalarDiv(SK_ScalarHalf, SkScalarSqrt(b*b + d*d)); | 
| + // This adjusts the "radius" to include the half-pixel border | 
| + SkScalar offsetDx = SkScalarDiv(geoDx, xRadius); | 
| + SkScalar offsetDy = SkScalarDiv(geoDy, yRadius); | 
| + | 
| + SkRect bounds = SkRect::MakeLTRB( | 
| + center.fX - xRadius - geoDx, | 
| + center.fY - yRadius - geoDy, | 
| + center.fX + xRadius + geoDx, | 
| + center.fY + yRadius + geoDy | 
| + ); | 
| + | 
| + verts[0].fPos = SkPoint::Make(bounds.fLeft, bounds.fTop); | 
| + verts[0].fOuterOffset = SkPoint::Make(-1.0f - offsetDx, -1.0f - offsetDy); | 
| + verts[0].fInnerOffset = SkPoint::Make(-innerRatioX - offsetDx, -innerRatioY - offsetDy); | 
| + | 
| + verts[1].fPos = SkPoint::Make(bounds.fRight, bounds.fTop); | 
| + verts[1].fOuterOffset = SkPoint::Make(1.0f + offsetDx, -1.0f - offsetDy); | 
| + verts[1].fInnerOffset = SkPoint::Make(innerRatioX + offsetDx, -innerRatioY - offsetDy); | 
| + | 
| + verts[2].fPos = SkPoint::Make(bounds.fLeft, bounds.fBottom); | 
| + verts[2].fOuterOffset = SkPoint::Make(-1.0f - offsetDx, 1.0f + offsetDy); | 
| + verts[2].fInnerOffset = SkPoint::Make(-innerRatioX - offsetDx, innerRatioY + offsetDy); | 
| + | 
| + verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom); | 
| + verts[3].fOuterOffset = SkPoint::Make(1.0f + offsetDx, 1.0f + offsetDy); | 
| + verts[3].fInnerOffset = SkPoint::Make(innerRatioX + offsetDx, innerRatioY + offsetDy); | 
| + | 
| + target->drawNonIndexed(kTriangleStrip_GrPrimitiveType, 0, 4, &bounds); | 
| + | 
| + return true; | 
| +} | 
| + | 
| /////////////////////////////////////////////////////////////////////////////// | 
| static const uint16_t gRRectIndices[] = { | 
| @@ -693,7 +963,7 @@ bool GrOvalRenderer::drawSimpleRRect(GrDrawTarget* target, GrContext* context, b | 
| bounds.outset(halfWidth, halfWidth); | 
| } | 
| - isStroked = (isStroked && innerRadius > 0); | 
| + isStroked = (isStroked && innerRadius > 0); | 
| GrEffectRef* effect = CircleEdgeEffect::Create(isStroked); | 
| static const int kCircleEdgeAttrIndex = 1; | 
| @@ -789,7 +1059,7 @@ bool GrOvalRenderer::drawSimpleRRect(GrDrawTarget* target, GrContext* context, b | 
| bounds.outset(scaledStroke.fX, scaledStroke.fY); | 
| } | 
| - isStroked = (isStroked && innerXRadius > 0 && innerYRadius > 0); | 
| + isStroked = (isStroked && innerXRadius > 0 && innerYRadius > 0); | 
| GrDrawTarget::AutoReleaseGeometry geo(target, 16, 0); | 
| if (!geo.succeeded()) { |