diff options
author | sugoi@google.com <sugoi@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2013-01-07 14:26:40 +0000 |
---|---|---|
committer | sugoi@google.com <sugoi@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2013-01-07 14:26:40 +0000 |
commit | e3453cbd20d00d685131a09d9141b1c70f0c5710 (patch) | |
tree | 23a998ded5198b71634b695dc3760eee4bca5a2e /experimental | |
parent | d68bc309bbc3f0f4c3107cf4fa60ce1ff4847b75 (diff) |
This CL introduces a new path renderer.
Here are the characteristics :
- It uses the original path, before stroking
- It supports traight lines only (no curves)
- It supports butt or square caps only
- It supports miter or bevel joins only
- No AA support
Support for these will be added step by step later on.
A first pass at the benchmarks on my linux machine gave me these approximate speed improvements (running all bench with the option '--forceAA 0') :
path_stroke_small_long_line 4X
path_stroke_small_sawtooth 4X
path_stroke_big_rect 4X
path_stroke_small_rect 6X
path_stroke_big_triangle 4X
path_stroke_small_triangle 10X
lines_1_BW 1.5X
dashline_2_square 1.5X
dashline_1_square 1.5X
Also note that I can't submit this code until GrDrawTarget::isOpaque() is implemented, unless I just disable my renderer completely for now.
BUG=chromium:135111
TEST=The following gms are affected and may require rebaselining : lineclosepath, linepath, strokes_poly
Review URL: https://codereview.appspot.com/7026049
git-svn-id: http://skia.googlecode.com/svn/trunk@7047 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'experimental')
-rw-r--r-- | experimental/StrokePathRenderer/GrStrokePathRenderer.cpp | 305 | ||||
-rw-r--r-- | experimental/StrokePathRenderer/GrStrokePathRenderer.h | 30 |
2 files changed, 335 insertions, 0 deletions
diff --git a/experimental/StrokePathRenderer/GrStrokePathRenderer.cpp b/experimental/StrokePathRenderer/GrStrokePathRenderer.cpp new file mode 100644 index 0000000000..e23d9ef078 --- /dev/null +++ b/experimental/StrokePathRenderer/GrStrokePathRenderer.cpp @@ -0,0 +1,305 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "GrStrokePathRenderer.h" + +#include "GrDrawTarget.h" +#include "SkPath.h" +#include "SkStrokeRec.h" + +namespace { + +bool is_clockwise(const SkVector& before, const SkVector& after) { + return before.cross(after) > 0; +} + +enum IntersectionType { + kNone_IntersectionType, + kIn_IntersectionType, + kOut_IntersectionType +}; + +IntersectionType intersection(const SkPoint& p1, const SkPoint& p2, + const SkPoint& p3, const SkPoint& p4, + SkPoint& res) { + // Store the values for fast access and easy + // equations-to-code conversion + SkScalar x1 = p1.x(), x2 = p2.x(), x3 = p3.x(), x4 = p4.x(); + SkScalar y1 = p1.y(), y2 = p2.y(), y3 = p3.y(), y4 = p4.y(); + + SkScalar d = SkScalarMul(x1 - x2, y3 - y4) - SkScalarMul(y1 - y2, x3 - x4); + // If d is zero, there is no intersection + if (SkScalarNearlyZero(d)) { + return kNone_IntersectionType; + } + + // Get the x and y + SkScalar pre = SkScalarMul(x1, y2) - SkScalarMul(y1, x2), + post = SkScalarMul(x3, y4) - SkScalarMul(y3, x4); + // Compute the point of intersection + res.set(SkScalarDiv(SkScalarMul(pre, x3 - x4) - SkScalarMul(x1 - x2, post), d), + SkScalarDiv(SkScalarMul(pre, y3 - y4) - SkScalarMul(y1 - y2, post), d)); + + // Check if the x and y coordinates are within both lines + return (res.x() < GrMin(x1, x2) || res.x() > GrMax(x1, x2) || + res.x() < GrMin(x3, x4) || res.x() > GrMax(x3, x4) || + res.y() < GrMin(y1, y2) || res.y() > GrMax(y1, y2) || + res.y() < GrMin(y3, y4) || res.y() > GrMax(y3, y4)) ? + kOut_IntersectionType : kIn_IntersectionType; +} + +} // namespace + +GrStrokePathRenderer::GrStrokePathRenderer() { +} + +bool GrStrokePathRenderer::canDrawPath(const SkPath& path, + const SkStrokeRec& stroke, + const GrDrawTarget* target, + bool antiAlias) const { + // FIXME : put the proper condition once GrDrawTarget::isOpaque is implemented + const bool isOpaque = true; // target->isOpaque(); + + // FIXME : remove this requirement once we have AA circles and implement the + // circle joins/caps appropriately in the ::onDrawPath() function. + const bool requiresAACircle = (stroke.getCap() == SkPaint::kRound_Cap) || + (stroke.getJoin() == SkPaint::kRound_Join); + + // Indices being stored in uint16, we don't want to overflow the indices capacity + static const int maxVBSize = 1 << 16; + const int maxNbVerts = (path.countPoints() + 1) * 5; + + // Check that the path contains no curved lines, only straight lines + static const uint32_t unsupportedMask = SkPath::kQuad_SegmentMask | SkPath::kCubic_SegmentMask; + + // Must not be filled nor hairline nor semi-transparent + // Note : May require a check to path.isConvex() if AA is supported + return ((stroke.getStyle() == SkStrokeRec::kStroke_Style) && (maxNbVerts < maxVBSize) && + !path.isInverseFillType() && isOpaque && !requiresAACircle && !antiAlias && + ((path.getSegmentMasks() & unsupportedMask) == 0)); +} + +bool GrStrokePathRenderer::onDrawPath(const SkPath& origPath, + const SkStrokeRec& stroke, + GrDrawTarget* target, + bool antiAlias) { + if (origPath.isEmpty()) { + return true; + } + + SkScalar width = stroke.getWidth(); + if (width <= 0) { + return false; + } + + // Get the join type + SkPaint::Join join = stroke.getJoin(); + SkScalar miterLimit = stroke.getMiter(); + SkScalar sqMiterLimit = SkScalarMul(miterLimit, miterLimit); + if ((join == SkPaint::kMiter_Join) && (miterLimit <= SK_Scalar1)) { + // If the miter limit is small, treat it as a bevel join + join = SkPaint::kBevel_Join; + } + const bool isMiter = (join == SkPaint::kMiter_Join); + const bool isBevel = (join == SkPaint::kBevel_Join); + SkScalar invMiterLimit = isMiter ? SK_Scalar1 / miterLimit : 0; + SkScalar invMiterLimitSq = SkScalarMul(invMiterLimit, invMiterLimit); + + // Allocate vertices + const int nbQuads = origPath.countPoints() + 1; // Could be "-1" if path is not closed + GrVertexLayout layout = 0; // Just 3D points + const int extraVerts = isMiter || isBevel ? 1 : 0; + const int maxVertexCount = nbQuads * (4 + extraVerts); + const int maxIndexCount = nbQuads * (6 + extraVerts * 3); // Each extra vert adds a triangle + GrDrawTarget::AutoReleaseGeometry arg(target, layout, maxVertexCount, maxIndexCount); + if (!arg.succeeded()) { + return false; + } + SkPoint* verts = reinterpret_cast<SkPoint*>(arg.vertices()); + uint16_t* idxs = reinterpret_cast<uint16_t*>(arg.indices()); + int vCount = 0, iCount = 0; + + // Transform the path into a list of triangles + SkPath::Iter iter(origPath, false); + SkPoint pts[4]; + const SkScalar radius = SkScalarMul(width, 0.5); + SkPoint *firstPt = verts, *lastPt = NULL; + SkVector firstDir, dir; + firstDir.set(0, 0); + dir.set(0, 0); + bool isOpen = true; + for(SkPath::Verb v = iter.next(pts); v != SkPath::kDone_Verb; v = iter.next(pts)) { + switch(v) { + case SkPath::kMove_Verb: + // This will already be handled as pts[0] of the 1st line + break; + case SkPath::kClose_Verb: + isOpen = (lastPt == NULL); + break; + case SkPath::kLine_Verb: + { + SkVector v0 = dir; + dir = pts[1] - pts[0]; + if (dir.setLength(radius)) { + SkVector dirT; + dirT.set(dir.fY, -dir.fX); // Get perpendicular direction + SkPoint l1a = pts[0]+dirT, l1b = pts[1]+dirT, + l2a = pts[0]-dirT, l2b = pts[1]-dirT; + SkPoint miterPt[2]; + bool useMiterPoint = false; + int idx0(-1), idx1(-1); + if (NULL == lastPt) { + firstDir = dir; + } else { + SkVector v1 = dir; + if (v0.normalize() && v1.normalize()) { + SkScalar dotProd = v0.dot(v1); + // No need for bevel or miter join if the angle + // is either 0 or 180 degrees + if (!SkScalarNearlyZero(dotProd + SK_Scalar1) && + !SkScalarNearlyZero(dotProd - SK_Scalar1)) { + bool ccw = !is_clockwise(v0, v1); + int offset = ccw ? 1 : 0; + idx0 = vCount-2+offset; + idx1 = vCount+offset; + const SkPoint* pt0 = &(lastPt[offset]); + const SkPoint* pt1 = ccw ? &l2a : &l1a; + switch(join) { + case SkPaint::kMiter_Join: + { + // *Note : Logic is from MiterJoiner + + // FIXME : Special case if we have a right angle ? + // if (SkScalarNearlyZero(dotProd)) {...} + + SkScalar sinHalfAngleSq = + SkScalarHalf(SK_Scalar1 + dotProd); + if (sinHalfAngleSq >= invMiterLimitSq) { + // Find the miter point (or points if it is further + // than the miter limit) + const SkPoint pt2 = *pt0+v0, pt3 = *pt1+v1; + if (intersection(*pt0, pt2, *pt1, pt3, miterPt[0]) != + kNone_IntersectionType) { + SkPoint miterPt0 = miterPt[0] - *pt0; + SkPoint miterPt1 = miterPt[0] - *pt1; + SkScalar sqDist0 = miterPt0.dot(miterPt0); + SkScalar sqDist1 = miterPt1.dot(miterPt1); + const SkScalar rSq = + SkScalarDiv(SkScalarMul(radius, radius), + sinHalfAngleSq); + const SkScalar sqRLimit = + SkScalarMul(sqMiterLimit, rSq); + if (sqDist0 > sqRLimit || sqDist1 > sqRLimit) { + if (sqDist1 > sqRLimit) { + v1.setLength(SkScalarSqrt(sqRLimit)); + miterPt[1] = *pt1+v1; + } else { + miterPt[1] = miterPt[0]; + } + if (sqDist0 > sqRLimit) { + v0.setLength(SkScalarSqrt(sqRLimit)); + miterPt[0] = *pt0+v0; + } + } else { + miterPt[1] = miterPt[0]; + } + useMiterPoint = true; + } + } + if (useMiterPoint && (miterPt[1] == miterPt[0])) { + break; + } + } + default: + case SkPaint::kBevel_Join: + { + // Note : This currently causes some overdraw where both + // lines initially intersect. We'd need to add + // another line intersection check here if the + // overdraw becomes an issue instead of using the + // current point directly. + + // Add center point + *verts++ = pts[0]; // Use current point directly + // This idx is passed the current point so increment it + ++idx1; + // Add center triangle + *idxs++ = idx0; + *idxs++ = vCount; + *idxs++ = idx1; + vCount++; + iCount += 3; + } + break; + } + } + } + } + *verts++ = l1a; + *verts++ = l2a; + lastPt = verts; + *verts++ = l1b; + *verts++ = l2b; + + if (useMiterPoint && (idx0 >= 0) && (idx1 >= 0)) { + firstPt[idx0] = miterPt[0]; + firstPt[idx1] = miterPt[1]; + } + + // 1st triangle + *idxs++ = vCount+0; + *idxs++ = vCount+2; + *idxs++ = vCount+1; + // 2nd triangle + *idxs++ = vCount+1; + *idxs++ = vCount+2; + *idxs++ = vCount+3; + + vCount += 4; + iCount += 6; + } + } + break; + case SkPath::kQuad_Verb: + case SkPath::kCubic_Verb: + GrAssert(!"Curves not supported!"); + default: + // Unhandled cases + GrAssert(false); + } + } + + if (isOpen) { + // Add caps + switch (stroke.getCap()) { + case SkPaint::kSquare_Cap: + firstPt[0] -= firstDir; + firstPt[1] -= firstDir; + lastPt [0] += dir; + lastPt [1] += dir; + break; + case SkPaint::kRound_Cap: + GrAssert(!"Round caps not supported!"); + default: // No cap + break; + } + } + + GrAssert(vCount <= maxVertexCount); + GrAssert(iCount <= maxIndexCount); + + if (vCount > 0) { + target->drawIndexed(kTriangles_GrPrimitiveType, + 0, // start vertex + 0, // start index + vCount, + iCount); + } + + return true; +} + diff --git a/experimental/StrokePathRenderer/GrStrokePathRenderer.h b/experimental/StrokePathRenderer/GrStrokePathRenderer.h new file mode 100644 index 0000000000..9cf34d8712 --- /dev/null +++ b/experimental/StrokePathRenderer/GrStrokePathRenderer.h @@ -0,0 +1,30 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "GrPathRenderer.h" + +// This path renderer is made to create geometry (i.e. primitives) from the original path (before +// the path is stroked) and render using the GPU directly rather than using any software rendering +// step. It can be rendered in a single pass for simple cases and use multiple passes for features +// like AA or opacity support. + +class GrStrokePathRenderer : public GrPathRenderer { + +public: + GrStrokePathRenderer(); + + virtual bool canDrawPath(const SkPath& path, + const SkStrokeRec& stroke, + const GrDrawTarget* target, + bool antiAlias) const SK_OVERRIDE; + +protected: + virtual bool onDrawPath(const SkPath& path, + const SkStrokeRec& stroke, + GrDrawTarget* target, + bool antiAlias) SK_OVERRIDE; +}; |