From 5242ed761fc60470f31ffa6ea529c2a2f6afe6f5 Mon Sep 17 00:00:00 2001 From: "commit-bot@chromium.org" Date: Thu, 5 Sep 2013 19:26:51 +0000 Subject: Add device-independent rendering of ovals, take two. This permits GPU support for arbitrary matrices. The only exception is not all stroked ovals are supported, as thin ovals + fat strokes do not produce elliptical borders. R=bsalomon@google.com, robertphillips@google.com Author: jvanverth@google.com Review URL: https://chromiumcodereview.appspot.com/23701013 git-svn-id: http://skia.googlecode.com/svn/trunk@11115 2bbb7eff-a529-9590-31e7-b0007b416f81 --- src/gpu/GrOvalRenderer.cpp | 302 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 286 insertions(+), 16 deletions(-) (limited to 'src/gpu/GrOvalRenderer.cpp') diff --git a/src/gpu/GrOvalRenderer.cpp b/src/gpu/GrOvalRenderer.cpp index 6965f9e580..f062256189 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::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(); + + 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(); + + 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(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()) { + 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(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(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()) { -- cgit v1.2.3