Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(459)

Unified Diff: src/gpu/GrOvalRenderer.cpp

Issue 23701013: Add device-independent rendering of ovals, take two. (Closed) Base URL: https://skia.googlecode.com/svn/trunk
Patch Set: A little more prettyprint Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « include/gpu/GrOvalRenderer.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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()) {
« no previous file with comments | « include/gpu/GrOvalRenderer.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698