diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h index 433c113a04..d3723c7380 100644 --- a/include/gpu/GrContext.h +++ b/include/gpu/GrContext.h @@ -439,6 +439,20 @@ public: const uint16_t indices[], int indexCount); + /** + * Draws an oval. + * + * @param paint describes how to color pixels. + * @param rect the bounding rect of the oval. + * @param strokeWidth if strokeWidth < 0, then the oval is filled, else + * the rect is stroked based on strokeWidth. If + * strokeWidth == 0, then the stroke is always a single + * pixel thick. + */ + void drawOval(const GrPaint& paint, + const GrRect& rect, + SkScalar strokeWidth); + /////////////////////////////////////////////////////////////////////////// // Misc. @@ -719,6 +733,9 @@ private: const GrDrawTarget* target, bool antiAlias); + void internalDrawPath(const GrPaint& paint, const GrPath& path, + GrPathFill fill, const GrPoint* translate); + /** * Flags to the internal read/write pixels funcs */ diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp index 8e45e15a0a..1abf682df5 100644 --- a/src/gpu/GrContext.cpp +++ b/src/gpu/GrContext.cpp @@ -1435,6 +1435,123 @@ void draw_around_inv_path(GrDrawTarget* target, } } +struct CircleVertex { + GrPoint fPos; + GrPoint fCenter; + GrScalar fOuterRadius; + GrScalar fInnerRadius; +}; + +/* Returns true if will map a circle to another circle. This can be true + * if the matrix only includes square-scale, rotation, translation. + */ +inline bool isSimilarityTransformation(const SkMatrix& matrix, + SkScalar tol = SK_ScalarNearlyZero) { + if (matrix.isIdentity() || matrix.getType() == SkMatrix::kTranslate_Mask) { + return true; + } + if (matrix.hasPerspective()) { + return false; + } + + SkScalar mx = matrix.get(SkMatrix::kMScaleX); + SkScalar sx = matrix.get(SkMatrix::kMSkewX); + SkScalar my = matrix.get(SkMatrix::kMScaleY); + SkScalar sy = matrix.get(SkMatrix::kMSkewY); + + if (mx == 0 && sx == 0 && my == 0 && sy == 0) { + return false; + } + + // it has scales or skews, but it could also be rotation, check it out. + SkVector vec[2]; + vec[0].set(mx, sx); + vec[1].set(sy, my); + + return SkScalarNearlyZero(vec[0].dot(vec[1]), SkScalarSquare(tol)) && + SkScalarNearlyEqual(vec[0].lengthSqd(), vec[1].lengthSqd(), + SkScalarSquare(tol)); +} + +} + +// TODO: strokeWidth can't be larger than zero right now. +// It will be fixed when drawPath() can handle strokes. +void GrContext::drawOval(const GrPaint& paint, + const GrRect& rect, + SkScalar strokeWidth) { + DrawCategory category = (DEFER_PATHS) ? kBuffered_DrawCategory : + kUnbuffered_DrawCategory; + GrDrawTarget* target = this->prepareToDraw(paint, category); + GrDrawState* drawState = target->drawState(); + GrMatrix vm = drawState->getViewMatrix(); + + if (!isSimilarityTransformation(vm) || + !paint.fAntiAlias || + rect.height() != rect.width()) { + SkPath path; + path.addOval(rect); + GrPathFill fill = (strokeWidth == 0) ? + kHairLine_PathFill : kWinding_PathFill; + this->internalDrawPath(paint, path, fill, NULL); + return; + } + + const GrRenderTarget* rt = drawState->getRenderTarget(); + if (NULL == rt) { + return; + } + + GrDrawTarget::AutoDeviceCoordDraw adcd(target, paint.getActiveStageMask()); + + GrVertexLayout layout = PaintStageVertexLayoutBits(paint, NULL); + layout |= GrDrawTarget::kEdge_VertexLayoutBit; + GrAssert(sizeof(CircleVertex) == GrDrawTarget::VertexSize(layout)); + + GrPoint center = GrPoint::Make(rect.centerX(), rect.centerY()); + GrScalar radius = SkScalarHalf(rect.width()); + + vm.mapPoints(¢er, 1); + radius = vm.mapRadius(radius); + + GrScalar outerRadius = radius; + GrScalar innerRadius = 0; + SkScalar halfWidth = 0; + if (strokeWidth == 0) { + halfWidth = SkScalarHalf(SK_Scalar1); + + outerRadius += halfWidth; + innerRadius = SkMaxScalar(0, radius - halfWidth); + } + + GrDrawTarget::AutoReleaseGeometry geo(target, layout, 4, 0); + if (!geo.succeeded()) { + GrPrintf("Failed to get space for vertices!\n"); + return; + } + + CircleVertex* verts = reinterpret_cast(geo.vertices()); + + SkScalar L = center.fX - outerRadius; + SkScalar R = center.fX + outerRadius; + SkScalar T = center.fY - outerRadius; + SkScalar B = center.fY + outerRadius; + + 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); + + for (int i = 0; i < 4; ++i) { + // this goes to fragment shader, it should be in y-points-up space. + verts[i].fCenter = SkPoint::Make(center.fX, rt->height() - center.fY); + + verts[i].fOuterRadius = outerRadius; + verts[i].fInnerRadius = innerRadius; + } + + drawState->setVertexEdgeType(GrDrawState::kCircle_EdgeType); + target->drawNonIndexed(kTriangleStrip_PrimitiveType, 0, 4); } @@ -1499,6 +1616,22 @@ void GrContext::drawPath(const GrPaint& paint, const GrPath& path, return; } + SkRect ovalRect; + if (!GrIsFillInverted(fill) && path.isOval(&ovalRect)) { + if (translate) { + ovalRect.offset(*translate); + } + SkScalar width = (fill == kHairLine_PathFill) ? 0 : -1; + this->drawOval(paint, ovalRect, width); + return; + } + + internalDrawPath(paint, path, fill, translate); +} + +void GrContext::internalDrawPath(const GrPaint& paint, const GrPath& path, + GrPathFill fill, const GrPoint* translate) { + // Note that below we may sw-rasterize the path into a scratch texture. // Scratch textures can be recycled after they are returned to the texture // cache. This presents a potential hazard for buffered drawing. However, diff --git a/src/gpu/GrDrawState.h b/src/gpu/GrDrawState.h index e5c30b6086..270912e7c5 100644 --- a/src/gpu/GrDrawState.h +++ b/src/gpu/GrDrawState.h @@ -539,6 +539,9 @@ public: * When specifying edges as vertex data this enum specifies what type of * edges are in use. The edges are always 4 GrScalars in memory, even when * the edge type requires fewer than 4. + * + * TODO: Fix the fact that HairLine and Circle edge types use y-down coords. + * (either adjust in VS or use origin_upper_left in GLSL) */ enum VertexEdgeType { /* 1-pixel wide line @@ -546,11 +549,15 @@ public: kHairLine_EdgeType, /* Quadratic specified by u^2-v canonical coords (only 2 components used). Coverage based on signed distance with negative - being inside, positive outside.*/ + being inside, positive outside. Edge specified in window space + (y-down) */ kQuad_EdgeType, /* Same as above but for hairline quadratics. Uses unsigned distance. Coverage is min(0, 1-distance). */ kHairQuad_EdgeType, + /* Circle specified as center_x, center_y, outer_radius, inner_radius + all in window space (y-down). */ + kCircle_EdgeType, kVertexEdgeTypeCnt }; diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp index fb98b4d3a7..3e1f1d40c2 100644 --- a/src/gpu/gl/GrGLProgram.cpp +++ b/src/gpu/gl/GrGLProgram.cpp @@ -471,10 +471,12 @@ void GrGLProgram::genEdgeCoverage(const GrGLContextInfo& gl, segments->fVSAttrs.push_back().set(kVec4f_GrSLType, GrGLShaderVar::kAttribute_TypeModifier, EDGE_ATTR_NAME); segments->fVSCode.appendf("\t%s = " EDGE_ATTR_NAME ";\n", vsName); - if (GrDrawState::kHairLine_EdgeType == fProgramDesc.fVertexEdgeType) { + switch (fProgramDesc.fVertexEdgeType) { + case GrDrawState::kHairLine_EdgeType: segments->fFSCode.appendf("\tfloat edgeAlpha = abs(dot(vec3(gl_FragCoord.xy,1), %s.xyz));\n", fsName); segments->fFSCode.append("\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n"); - } else if (GrDrawState::kQuad_EdgeType == fProgramDesc.fVertexEdgeType) { + break; + case GrDrawState::kQuad_EdgeType: segments->fFSCode.append("\tfloat edgeAlpha;\n"); // keep the derivative instructions outside the conditional segments->fFSCode.appendf("\tvec2 duvdx = dFdx(%s.xy);\n", fsName); @@ -492,8 +494,8 @@ void GrGLProgram::genEdgeCoverage(const GrGLContextInfo& gl, if (kES2_GrGLBinding == gl.binding()) { segments->fHeader.printf("#extension GL_OES_standard_derivatives: enable\n"); } - } else { - GrAssert(GrDrawState::kHairQuad_EdgeType == fProgramDesc.fVertexEdgeType); + break; + case GrDrawState::kHairQuad_EdgeType: segments->fFSCode.appendf("\tvec2 duvdx = dFdx(%s.xy);\n", fsName); segments->fFSCode.appendf("\tvec2 duvdy = dFdy(%s.xy);\n", fsName); segments->fFSCode.appendf("\tvec2 gF = vec2(2.0*%s.x*duvdx.x - duvdx.y,\n" @@ -505,6 +507,17 @@ void GrGLProgram::genEdgeCoverage(const GrGLContextInfo& gl, if (kES2_GrGLBinding == gl.binding()) { segments->fHeader.printf("#extension GL_OES_standard_derivatives: enable\n"); } + break; + case GrDrawState::kCircle_EdgeType: + segments->fFSCode.append("\tfloat edgeAlpha;\n"); + segments->fFSCode.appendf("\tfloat d = distance(gl_FragCoord.xy, %s.xy);\n", fsName); + segments->fFSCode.appendf("\tfloat outerAlpha = smoothstep(d - 0.5, d + 0.5, %s.z);\n", fsName); + segments->fFSCode.appendf("\tfloat innerAlpha = %s.w == 0.0 ? 1.0 : smoothstep(%s.w - 0.5, %s.w + 0.5, d);\n", fsName, fsName, fsName); + segments->fFSCode.append("\tedgeAlpha = outerAlpha * innerAlpha;\n"); + break; + default: + GrCrash("Unknown Edge Type!"); + break; } *coverageVar = "edgeAlpha"; } else {