From ce1c8869cf5e37753f9c8193314d93bff897d014 Mon Sep 17 00:00:00 2001 From: bsalomon Date: Mon, 15 Dec 2014 07:11:22 -0800 Subject: [PATCH] Do circle anti-aliasing in normalized space to avoid precision issues with half-floats on Adreno. Review URL: https://codereview.chromium.org/761593006 --- expectations/gm/ignored-tests.txt | 8 ++++++ gm/circles.cpp | 11 ++++++++ src/gpu/GrOvalRenderer.cpp | 54 ++++++++++++++++++++++----------------- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/expectations/gm/ignored-tests.txt b/expectations/gm/ignored-tests.txt index 4457741..1c605f3 100644 --- a/expectations/gm/ignored-tests.txt +++ b/expectations/gm/ignored-tests.txt @@ -59,3 +59,11 @@ drawbitmapmatrix #junov skbug.com/3176 pictureimagefilter + +#bsalomon - circle normalization 12/11/2014 +aarectmodes +hairmodes +c_gms +mixed_xfermodes +circles +roundrects \ No newline at end of file diff --git a/gm/circles.cpp b/gm/circles.cpp index 734fcb5..2c9368b 100644 --- a/gm/circles.cpp +++ b/gm/circles.cpp @@ -157,6 +157,17 @@ protected: virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE { SkLCGRandom rand; + // Draw a giant AA circle as the background. + SkISize size = this->getISize(); + SkScalar giantRadius = SkTMin(SkIntToScalar(size.fWidth), + SkIntToScalar(size.fHeight)) / 2.f; + SkPoint giantCenter = SkPoint::Make(SkIntToScalar(size.fWidth/2), + SkIntToScalar(size.fHeight/2)); + SkPaint giantPaint; + giantPaint.setAntiAlias(true); + giantPaint.setColor(0x80808080); + canvas->drawCircle(giantCenter.fX, giantCenter.fY, giantRadius, giantPaint); + canvas->translate(20 * SK_Scalar1, 20 * SK_Scalar1); int i; for (i = 0; i < fPaints.count(); ++i) { diff --git a/src/gpu/GrOvalRenderer.cpp b/src/gpu/GrOvalRenderer.cpp index 22a18d2..e1a29f5 100644 --- a/src/gpu/GrOvalRenderer.cpp +++ b/src/gpu/GrOvalRenderer.cpp @@ -53,8 +53,14 @@ inline bool circle_stays_circle(const SkMatrix& m) { /////////////////////////////////////////////////////////////////////////////// /** - * The output of this effect is a modulation of the input color and coverage for a circle, - * specified as offset_x, offset_y (both from center point), outer radius and inner radius. + * The output of this effect is a modulation of the input color and coverage for a circle. It + * operates in a space normalized by the circle radius (outer radius in the case of a stroke) + * with origin at the circle center. Two vertex attributes are used: + * vec2f : position in device space of the bounding geometry vertices + * vec4f : (p.xy, outerRad, innerRad) + * p is the position in the normalized space. + * outerRad is the outerRadius in device space. + * innerRad is the innerRadius in normalized space (ignored if not stroking). */ class CircleEdgeEffect : public GrGeometryProcessor { @@ -94,10 +100,10 @@ public: GrGLGPFragmentBuilder* fsBuilder = args.fPB->getFragmentShaderBuilder(); fsBuilder->codeAppendf("float d = length(%s.xy);", v.fsIn()); - fsBuilder->codeAppendf("float edgeAlpha = clamp(%s.z - d, 0.0, 1.0);", v.fsIn()); + fsBuilder->codeAppendf("float edgeAlpha = clamp(%s.z * (1.0 - d), 0.0, 1.0);", v.fsIn()); if (ce.isStroked()) { - fsBuilder->codeAppendf("float innerAlpha = clamp(d - %s.w, 0.0, 1.0);", - v.fsIn()); + fsBuilder->codeAppendf("float innerAlpha = clamp(%s.z * (d - %s.w), 0.0, 1.0);", + v.fsIn(), v.fsIn()); fsBuilder->codeAppend("edgeAlpha *= innerAlpha;"); } @@ -574,10 +580,10 @@ void GrOvalRenderer::drawCircle(GrDrawTarget* target, CircleVertex* verts = reinterpret_cast(geo.vertices()); - // The radii are outset for two reasons. First, it allows the shader to simply perform - // clamp(distance-to-center - radius, 0, 1). Second, the outer radius is used to compute the - // verts of the bounding box that is rendered and the outset ensures the box will cover all - // pixels partially covered by the circle. + // The radii are outset for two reasons. First, it allows the shader to simply perform simpler + // computation because the computed alpha is zero, rather than 50%, at the radius. + // Second, the outer radius is used to compute the verts of the bounding box that is rendered + // and the outset ensures the box will cover all partially covered by the circle. outerRadius += SK_ScalarHalf; innerRadius -= SK_ScalarHalf; @@ -588,23 +594,25 @@ void GrOvalRenderer::drawCircle(GrDrawTarget* target, center.fY + outerRadius ); + // The inner radius in the vertex data must be specified in normalized space. + innerRadius = innerRadius / outerRadius; verts[0].fPos = SkPoint::Make(bounds.fLeft, bounds.fTop); - verts[0].fOffset = SkPoint::Make(-outerRadius, -outerRadius); + verts[0].fOffset = SkPoint::Make(-1, -1); verts[0].fOuterRadius = outerRadius; verts[0].fInnerRadius = innerRadius; verts[1].fPos = SkPoint::Make(bounds.fLeft, bounds.fBottom); - verts[1].fOffset = SkPoint::Make(-outerRadius, outerRadius); + verts[1].fOffset = SkPoint::Make(-1, 1); verts[1].fOuterRadius = outerRadius; verts[1].fInnerRadius = innerRadius; verts[2].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom); - verts[2].fOffset = SkPoint::Make(outerRadius, outerRadius); + verts[2].fOffset = SkPoint::Make(1, 1); verts[2].fOuterRadius = outerRadius; verts[2].fInnerRadius = innerRadius; verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fTop); - verts[3].fOffset = SkPoint::Make(outerRadius, -outerRadius); + verts[3].fOffset = SkPoint::Make(1, -1); verts[3].fOuterRadius = outerRadius; verts[3].fInnerRadius = innerRadius; @@ -1083,9 +1091,10 @@ bool GrOvalRenderer::drawRRect(GrDrawTarget* target, CircleVertex* verts = reinterpret_cast(geo.vertices()); // The radii are outset for two reasons. First, it allows the shader to simply perform - // clamp(distance-to-center - radius, 0, 1). Second, the outer radius is used to compute the - // verts of the bounding box that is rendered and the outset ensures the box will cover all - // pixels partially covered by the circle. + // simpler computation because the computed alpha is zero, rather than 50%, at the radius. + // Second, the outer radius is used to compute the verts of the bounding box that is + // rendered and the outset ensures the box will cover all partially covered by the rrect + // corners. outerRadius += SK_ScalarHalf; innerRadius -= SK_ScalarHalf; @@ -1098,15 +1107,12 @@ bool GrOvalRenderer::drawRRect(GrDrawTarget* target, bounds.fBottom - outerRadius, bounds.fBottom }; - SkScalar yOuterRadii[4] = { - -outerRadius, - 0, - 0, - outerRadius - }; + SkScalar yOuterRadii[4] = {-1, 0, 0, 1 }; + // The inner radius in the vertex data must be specified in normalized space. + innerRadius = innerRadius / outerRadius; for (int i = 0; i < 4; ++i) { verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]); - verts->fOffset = SkPoint::Make(-outerRadius, yOuterRadii[i]); + verts->fOffset = SkPoint::Make(-1, yOuterRadii[i]); verts->fOuterRadius = outerRadius; verts->fInnerRadius = innerRadius; verts++; @@ -1124,7 +1130,7 @@ bool GrOvalRenderer::drawRRect(GrDrawTarget* target, verts++; verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]); - verts->fOffset = SkPoint::Make(outerRadius, yOuterRadii[i]); + verts->fOffset = SkPoint::Make(1, yOuterRadii[i]); verts->fOuterRadius = outerRadius; verts->fInnerRadius = innerRadius; verts++; -- 2.7.4