draw circle paths directly via GPU
Review URL: http://codereview.appspot.com/5696086/
Submitted on behalf of Guanqun.Lu@gmail.com
git-svn-id: http://skia.googlecode.com/svn/trunk@3772 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index 433c113..d3723c7 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -439,6 +439,20 @@
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 @@
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 8e45e15..1abf682 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -1435,6 +1435,123 @@
}
}
+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<CircleVertex*>(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 @@
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 e5c30b6..270912e 100644
--- a/src/gpu/GrDrawState.h
+++ b/src/gpu/GrDrawState.h
@@ -539,6 +539,9 @@
* 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 @@
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 fb98b4d..3e1f1d4 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -471,10 +471,12 @@
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 @@
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 @@
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 {