From: bsalomon@google.com Date: Mon, 12 Sep 2011 14:59:34 +0000 (+0000) Subject: Add perspective support to the gpu aa hairline renderer. X-Git-Tag: submit/tizen/20180928.044319~17693 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=dbeeac33329f5fd7dbd3514cd7189ca6ed080476;p=platform%2Fupstream%2FlibSkiaSharp.git Add perspective support to the gpu aa hairline renderer. Review URL: http://codereview.appspot.com/4969071/ git-svn-id: http://skia.googlecode.com/svn/trunk@2249 2bbb7eff-a529-9590-31e7-b0007b416f81 --- 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 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;