aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gpu/src/GrAAHairLinePathRenderer.cpp238
-rw-r--r--gpu/src/GrAAHairLinePathRenderer.h1
-rw-r--r--include/core/SkGeometry.h1
-rw-r--r--include/core/SkPoint.h10
4 files changed, 174 insertions, 76 deletions
diff --git a/gpu/src/GrAAHairLinePathRenderer.cpp b/gpu/src/GrAAHairLinePathRenderer.cpp
index 13aa00158d..eca295fbdb 100644
--- a/gpu/src/GrAAHairLinePathRenderer.cpp
+++ b/gpu/src/GrAAHairLinePathRenderer.cpp
@@ -3,6 +3,7 @@
#include "GrContext.h"
#include "GrGpu.h"
#include "GrIndexBuffer.h"
+#include "GrPathUtils.h"
#include "SkGeometry.h"
#include "SkTemplates.h"
@@ -116,9 +117,7 @@ bool GrAAHairLinePathRenderer::supportsAA(GrDrawTarget* target,
bool GrAAHairLinePathRenderer::canDrawPath(const GrDrawTarget* target,
const SkPath& path,
GrPathFill fill) const {
- // TODO: support perspective
- return kHairLine_PathFill == fill &&
- !target->getViewMatrix().hasPerspective();
+ return kHairLine_PathFill == fill;
}
void GrAAHairLinePathRenderer::pathWillClear() {
@@ -145,6 +144,7 @@ typedef GrTArray<int, true> IntArray;
* We convert cubics to quadratics (for now).
*/
void convert_noninflect_cubic_to_quads(const SkPoint p[4],
+ SkScalar tolScale,
PtArray* quads,
int sublevel = 0) {
SkVector ab = p[1];
@@ -153,8 +153,9 @@ void convert_noninflect_cubic_to_quads(const SkPoint p[4],
dc -= p[3];
static const SkScalar gLengthScale = 3 * SK_Scalar1 / 2;
- static const SkScalar gDistanceSqdTol = 2 * SK_Scalar1;
- static const int kMaxSubdivs = 30;
+ // base tolerance is 2 pixels in dev coords.
+ const SkScalar distanceSqdTol = SkScalarMul(tolScale, 2 * SK_Scalar1);
+ static const int kMaxSubdivs = 10;
ab.scale(gLengthScale);
dc.scale(gLengthScale);
@@ -165,7 +166,7 @@ void convert_noninflect_cubic_to_quads(const SkPoint p[4],
c1 += dc;
SkScalar dSqd = c0.distanceToSqd(c1);
- if (sublevel > kMaxSubdivs || dSqd <= gDistanceSqdTol) {
+ if (sublevel > kMaxSubdivs || dSqd <= distanceSqdTol) {
SkPoint cAvg = c0;
cAvg += c1;
cAvg.scale(SK_ScalarHalf);
@@ -180,18 +181,22 @@ void convert_noninflect_cubic_to_quads(const SkPoint p[4],
} else {
SkPoint choppedPts[7];
SkChopCubicAtHalf(p, choppedPts);
- convert_noninflect_cubic_to_quads(choppedPts + 0, quads, sublevel + 1);
- convert_noninflect_cubic_to_quads(choppedPts + 3, quads, sublevel + 1);
+ convert_noninflect_cubic_to_quads(choppedPts + 0, tolScale,
+ quads, sublevel + 1);
+ convert_noninflect_cubic_to_quads(choppedPts + 3, tolScale,
+ quads, sublevel + 1);
}
}
-void convert_cubic_to_quads(const SkPoint p[4], PtArray* quads) {
+void convert_cubic_to_quads(const SkPoint p[4],
+ SkScalar tolScale,
+ PtArray* quads) {
SkPoint chopped[13];
int count = SkChopCubicAtInflections(p, chopped);
for (int i = 0; i < count; ++i) {
SkPoint* cubic = chopped + 3*i;
- convert_noninflect_cubic_to_quads(cubic, quads);
+ convert_noninflect_cubic_to_quads(cubic, tolScale, quads);
}
}
@@ -267,74 +272,117 @@ int num_quad_subdivs(const SkPoint p[3]) {
}
}
-int get_lines_and_quads(const SkPath& path, const SkMatrix& m, GrIRect clip,
- PtArray* lines, PtArray* quads,
- IntArray* quadSubdivCnts) {
+/**
+ * Generates the lines and quads to be rendered. Lines are always recorded in
+ * device space. We will do a device space bloat to account for the 1pixel
+ * thickness.
+ * Quads are recorded in device space unless m contains
+ * perspective, then in they are in src space. We do this because we will
+ * subdivide large quads to reduce over-fill. This subdivision has to be
+ * performed before applying the perspective matrix.
+ */
+int generate_lines_and_quads(const SkPath& path,
+ const SkMatrix& m,
+ const SkVector& translate,
+ GrIRect clip,
+ PtArray* lines,
+ PtArray* quads,
+ IntArray* quadSubdivCnts) {
SkPath::Iter iter(path, false);
int totalQuadCount = 0;
GrRect bounds;
GrIRect ibounds;
+
+ bool persp = m.hasPerspective();
+
for (;;) {
GrPoint pts[4];
+ GrPoint devPts[4];
GrPathCmd cmd = (GrPathCmd)iter.next(pts);
switch (cmd) {
case kMove_PathCmd:
break;
case kLine_PathCmd:
- m.mapPoints(pts,2);
- bounds.setBounds(pts, 2);
+ SkPoint::Offset(pts, 2, translate);
+ m.mapPoints(devPts, pts, 2);
+ bounds.setBounds(devPts, 2);
bounds.outset(SK_Scalar1, SK_Scalar1);
bounds.roundOut(&ibounds);
if (SkIRect::Intersects(clip, ibounds)) {
- lines->push_back() = pts[0];
- lines->push_back() = pts[1];
+ lines->push_back() = devPts[0];
+ lines->push_back() = devPts[1];
}
break;
- case kQuadratic_PathCmd: {
- bounds.setBounds(pts, 3);
+ case kQuadratic_PathCmd:
+ SkPoint::Offset(pts, 3, translate);
+ m.mapPoints(devPts, pts, 3);
+ bounds.setBounds(devPts, 3);
bounds.outset(SK_Scalar1, SK_Scalar1);
bounds.roundOut(&ibounds);
if (SkIRect::Intersects(clip, ibounds)) {
- m.mapPoints(pts, 3);
- int subdiv = num_quad_subdivs(pts);
+ int subdiv = num_quad_subdivs(devPts);
GrAssert(subdiv >= -1);
if (-1 == subdiv) {
- lines->push_back() = pts[0];
- lines->push_back() = pts[1];
- lines->push_back() = pts[1];
- lines->push_back() = pts[2];
+ lines->push_back() = devPts[0];
+ lines->push_back() = devPts[1];
+ lines->push_back() = devPts[1];
+ lines->push_back() = devPts[2];
} else {
- quads->push_back() = pts[0];
- quads->push_back() = pts[1];
- quads->push_back() = pts[2];
+ // when in perspective keep quads in src space
+ SkPoint* qPts = persp ? pts : devPts;
+ quads->push_back() = qPts[0];
+ quads->push_back() = qPts[1];
+ quads->push_back() = qPts[2];
quadSubdivCnts->push_back() = subdiv;
totalQuadCount += 1 << subdiv;
}
}
- } break;
- case kCubic_PathCmd: {
- bounds.setBounds(pts, 4);
+ break;
+ case kCubic_PathCmd:
+ SkPoint::Offset(pts, 4, translate);
+ m.mapPoints(devPts, pts, 4);
+ bounds.setBounds(devPts, 4);
bounds.outset(SK_Scalar1, SK_Scalar1);
bounds.roundOut(&ibounds);
if (SkIRect::Intersects(clip, ibounds)) {
- m.mapPoints(pts, 4);
SkPoint stackStorage[32];
PtArray q((void*)stackStorage, 32);
- convert_cubic_to_quads(pts, &q);
+ // in perspective have to do conversion in src space
+ if (persp) {
+ SkScalar tolScale =
+ GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m,
+ path.getBounds());
+ convert_cubic_to_quads(pts, tolScale, &q);
+ } else {
+ convert_cubic_to_quads(devPts, SK_Scalar1, &q);
+ }
for (int i = 0; i < q.count(); i += 3) {
- bounds.setBounds(&q[i], 3);
+ SkPoint* qInDevSpace;
+ // bounds has to be calculated in device space, but q is
+ // in src space when there is perspective.
+ if (persp) {
+ m.mapPoints(devPts, &q[i], 3);
+ bounds.setBounds(devPts, 3);
+ qInDevSpace = devPts;
+ } else {
+ bounds.setBounds(&q[i], 3);
+ qInDevSpace = &q[i];
+ }
bounds.outset(SK_Scalar1, SK_Scalar1);
bounds.roundOut(&ibounds);
if (SkIRect::Intersects(clip, ibounds)) {
- int subdiv = num_quad_subdivs(&q[i]);
+ int subdiv = num_quad_subdivs(qInDevSpace);
GrAssert(subdiv >= -1);
if (-1 == subdiv) {
- lines->push_back() = q[0 + i];
- lines->push_back() = q[1 + i];
- lines->push_back() = q[1 + i];
- lines->push_back() = q[2 + i];
+ // lines should always be in device coords
+ lines->push_back() = qInDevSpace[0];
+ lines->push_back() = qInDevSpace[1];
+ lines->push_back() = qInDevSpace[1];
+ lines->push_back() = qInDevSpace[2];
} else {
+ // q is already in src space when there is no
+ // perspective and dev coords otherwise.
quads->push_back() = q[0 + i];
quads->push_back() = q[1 + i];
quads->push_back() = q[2 + i];
@@ -344,7 +392,7 @@ int get_lines_and_quads(const SkPath& path, const SkMatrix& m, GrIRect clip,
}
}
}
- } break;
+ break;
case kClose_PathCmd:
break;
case kEnd_PathCmd:
@@ -387,30 +435,16 @@ void intersect_lines(const SkPoint& ptA, const SkVector& normA,
result->fY = SkScalarMul(result->fY, wInv);
}
-void bloat_quad(const SkPoint qpts[3], Vertex verts[kVertsPerQuad]) {
+void bloat_quad(const SkPoint qpts[3], const GrMatrix* toDevice,
+ const GrMatrix* toSrc, Vertex verts[kVertsPerQuad]) {
+ GrAssert(!toDevice == !toSrc);
// original quad is specified by tri a,b,c
- const SkPoint& a = qpts[0];
- const SkPoint& b = qpts[1];
- const SkPoint& c = qpts[2];
- // make a new poly where we replace a and c by a 1-pixel wide edges orthog
- // to edges ab and bc:
- //
- // before | after
- // | b0
- // b |
- // |
- // | a0 c0
- // a c | a1 c1
- //
- // edges a0->b0 and b0->c0 are parallel to original edges a->b and b->c,
- // respectively.
- Vertex& a0 = verts[0];
- Vertex& a1 = verts[1];
- Vertex& b0 = verts[2];
- Vertex& c0 = verts[3];
- Vertex& c1 = verts[4];
+ SkPoint a = qpts[0];
+ SkPoint b = qpts[1];
+ SkPoint c = qpts[2];
// compute a matrix that goes from device coords to U,V quad params
+ // this should be in the src space, not dev coords, when we have perspective
SkMatrix DevToUV;
DevToUV.setAll(a.fX, b.fX, c.fX,
a.fY, b.fY, c.fY,
@@ -428,6 +462,29 @@ void bloat_quad(const SkPoint qpts[3], Vertex verts[kVertsPerQuad]) {
DevToUV.setPerspX(0);
DevToUV.setPerspY(0);
+ if (toDevice) {
+ toDevice->mapPoints(&a, 1);
+ toDevice->mapPoints(&b, 1);
+ toDevice->mapPoints(&c, 1);
+ }
+ // make a new poly where we replace a and c by a 1-pixel wide edges orthog
+ // to edges ab and bc:
+ //
+ // before | after
+ // | b0
+ // b |
+ // |
+ // | a0 c0
+ // a c | a1 c1
+ //
+ // edges a0->b0 and b0->c0 are parallel to original edges a->b and b->c,
+ // respectively.
+ Vertex& a0 = verts[0];
+ Vertex& a1 = verts[1];
+ Vertex& b0 = verts[2];
+ Vertex& c0 = verts[3];
+ Vertex& c1 = verts[4];
+
SkVector ab = b;
ab -= a;
SkVector ac = c;
@@ -464,27 +521,33 @@ void bloat_quad(const SkPoint qpts[3], Vertex verts[kVertsPerQuad]) {
intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos);
+ if (toSrc) {
+ toSrc->mapPointsWithStride(&verts[0].fPos, sizeof(Vertex), kVertsPerQuad);
+ }
DevToUV.mapPointsWithStride(&verts[0].fQuadCoord,
&verts[0].fPos, sizeof(Vertex), kVertsPerQuad);
}
void add_quads(const SkPoint p[3],
int subdiv,
+ const GrMatrix* toDevice,
+ const GrMatrix* toSrc,
Vertex** vert) {
GrAssert(subdiv >= 0);
if (subdiv) {
SkPoint newP[5];
SkChopQuadAtHalf(p, newP);
- add_quads(newP + 0, subdiv-1, vert);
- add_quads(newP + 2, subdiv-1, vert);
+ add_quads(newP + 0, subdiv-1, toDevice, toSrc, vert);
+ add_quads(newP + 2, subdiv-1, toDevice, toSrc, vert);
} else {
- bloat_quad(p, *vert);
+ bloat_quad(p, toDevice, toSrc, *vert);
*vert += kVertsPerQuad;
}
}
void add_line(const SkPoint p[2],
int rtHeight,
+ const SkMatrix* toSrc,
Vertex** vert) {
const SkPoint& a = p[0];
const SkPoint& b = p[1];
@@ -516,6 +579,11 @@ void add_line(const SkPoint p[2],
(*vert)[i].fLine.fB = normal.fY;
(*vert)[i].fLine.fC = lineC;
}
+ if (NULL != toSrc) {
+ toSrc->mapPointsWithStride(&(*vert)->fPos,
+ sizeof(Vertex),
+ kVertsPerLineSeg);
+ }
} else {
// just make it degenerate and likely offscreen
(*vert)[0].fPos.set(SK_ScalarMax, SK_ScalarMax);
@@ -541,8 +609,12 @@ bool GrAAHairLinePathRenderer::createGeom(GrDrawTarget::StageBitfield stages) {
clip.setLargest();
}
+ // If none of the inputs that affect generation of path geometry have
+ // have changed since last previous path draw then we can reuse the
+ // previous geoemtry.
if (stages == fPreviousStages &&
fPreviousViewMatrix == fTarget->getViewMatrix() &&
+ fPreviousTranslate == fTranslate &&
rtHeight == fPreviousRTHeight &&
fClipRect == clip) {
return true;
@@ -556,15 +628,14 @@ bool GrAAHairLinePathRenderer::createGeom(GrDrawTarget::StageBitfield stages) {
}
GrMatrix viewM = fTarget->getViewMatrix();
- viewM.preTranslate(fTranslate.fX, fTranslate.fY);
GrAlignedSTStorage<128, GrPoint> lineStorage;
GrAlignedSTStorage<128, GrPoint> quadStorage;
PtArray lines(&lineStorage);
PtArray quads(&quadStorage);
IntArray qSubdivs;
- fQuadCnt = get_lines_and_quads(*fPath, viewM, clip,
- &lines, &quads, &qSubdivs);
+ fQuadCnt = generate_lines_and_quads(*fPath, viewM, fTranslate, clip,
+ &lines, &quads, &qSubdivs);
fLineSegmentCnt = lines.count() / 2;
int vertCnt = kVertsPerLineSeg * fLineSegmentCnt + kVertsPerQuad * fQuadCnt;
@@ -575,38 +646,53 @@ bool GrAAHairLinePathRenderer::createGeom(GrDrawTarget::StageBitfield stages) {
if (!fTarget->reserveVertexSpace(layout, vertCnt, (void**)&verts)) {
return false;
}
+ Vertex* base = verts;
+
+ const GrMatrix* toDevice = NULL;
+ const GrMatrix* toSrc = NULL;
+ GrMatrix ivm;
+
+ if (viewM.hasPerspective()) {
+ if (viewM.invert(&ivm)) {
+ toDevice = &viewM;
+ toSrc = &ivm;
+ }
+ }
for (int i = 0; i < fLineSegmentCnt; ++i) {
- add_line(&lines[2*i], rtHeight, &verts);
+ add_line(&lines[2*i], rtHeight, toSrc, &verts);
}
+
int unsubdivQuadCnt = quads.count() / 3;
for (int i = 0; i < unsubdivQuadCnt; ++i) {
GrAssert(qSubdivs[i] >= 0);
- add_quads(&quads[3*i], qSubdivs[i], &verts);
+ add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts);
}
fPreviousStages = stages;
fPreviousViewMatrix = fTarget->getViewMatrix();
fPreviousRTHeight = rtHeight;
fClipRect = clip;
+ fPreviousTranslate = fTranslate;
return true;
}
void GrAAHairLinePathRenderer::drawPath(GrDrawTarget::StageBitfield stages) {
- GrDrawTarget::AutoStateRestore asr(fTarget);
-
- GrMatrix ivm;
if (!this->createGeom(stages)) {
return;
}
- if (fTarget->getViewInverse(&ivm)) {
- fTarget->preConcatSamplerMatrices(stages, ivm);
+ GrDrawTarget::AutoStateRestore asr;
+ if (!fTarget->getViewMatrix().hasPerspective()) {
+ asr.set(fTarget);
+ GrMatrix ivm;
+ if (fTarget->getViewInverse(&ivm)) {
+ fTarget->preConcatSamplerMatrices(stages, ivm);
+ }
+ fTarget->setViewMatrix(GrMatrix::I());
}
- fTarget->setViewMatrix(GrMatrix::I());
-
// TODO: See whether rendering lines as degenerate quads improves perf
// when we have a mix
fTarget->setIndexSourceToBuffer(fLinesIndexBuffer);
diff --git a/gpu/src/GrAAHairLinePathRenderer.h b/gpu/src/GrAAHairLinePathRenderer.h
index c7d2dc722a..c1d61dcaf7 100644
--- a/gpu/src/GrAAHairLinePathRenderer.h
+++ b/gpu/src/GrAAHairLinePathRenderer.h
@@ -47,6 +47,7 @@ private:
// have to recreate geometry if stages in use changes :(
GrDrawTarget::StageBitfield fPreviousStages;
int fPreviousRTHeight;
+ SkVector fPreviousTranslate;
GrIRect fClipRect;
// this path renderer draws everything in device coordinates
diff --git a/include/core/SkGeometry.h b/include/core/SkGeometry.h
index 2b1072ba0f..26f27babf7 100644
--- a/include/core/SkGeometry.h
+++ b/include/core/SkGeometry.h
@@ -147,6 +147,7 @@ int SkFindCubicInflections(const SkPoint src[4], SkScalar tValues[2]);
/** Return 1 for no chop, 2 for having chopped the cubic at a single
inflection point, 3 for having chopped at 2 inflection points.
+ dst will hold the resulting 1, 2, or 3 cubics.
*/
int SkChopCubicAtInflections(const SkPoint src[4], SkPoint dst[10]);
diff --git a/include/core/SkPoint.h b/include/core/SkPoint.h
index 8ba2cbcaa6..fedc404a33 100644
--- a/include/core/SkPoint.h
+++ b/include/core/SkPoint.h
@@ -189,6 +189,16 @@ struct SK_API SkPoint {
}
void setRectFan(SkScalar l, SkScalar t, SkScalar r, SkScalar b, size_t stride);
+ static void Offset(SkPoint points[], int count, const SkPoint& offset) {
+ Offset(points, count, offset.fX, offset.fY);
+ }
+
+ static void Offset(SkPoint points[], int count, SkScalar dx, SkScalar dy) {
+ for (int i = 0; i < count; ++i) {
+ points[i].offset(dx, dy);
+ }
+ }
+
void offset(SkScalar dx, SkScalar dy) {
fX += dx;
fY += dy;