From aeb2160b1dd34f8e640e8e56544fe407d4ff6311 Mon Sep 17 00:00:00 2001 From: "bsalomon@google.com" Date: Tue, 30 Aug 2011 18:13:44 +0000 Subject: Add GrAAHairLinePathRenderer Review URL: http://codereview.appspot.com/4926045 git-svn-id: http://skia.googlecode.com/svn/trunk@2196 2bbb7eff-a529-9590-31e7-b0007b416f81 --- gpu/src/GrAAHairLinePathRenderer.cpp | 633 ++++++++++++++++++++++++++++++ gpu/src/GrAAHairLinePathRenderer.h | 62 +++ gpu/src/GrAddPathRenderers_aahairline.cpp | 20 + gpu/src/GrContext.cpp | 7 +- gpu/src/GrDefaultPathRenderer.h | 3 +- gpu/src/GrDrawTarget.cpp | 87 +++- gpu/src/GrDrawTarget.h | 74 +++- gpu/src/GrGLProgram.cpp | 24 +- gpu/src/GrGLProgram.h | 4 + gpu/src/GrGpuGLFixed.cpp | 11 +- gpu/src/GrGpuGLShaders.cpp | 60 ++- gpu/src/GrPathRenderer.h | 17 +- gpu/src/GrPathRendererChain.cpp | 2 +- gpu/src/GrTesselatedPathRenderer.cpp | 3 +- gpu/src/GrTesselatedPathRenderer.h | 3 +- gyp/SampleApp.gyp | 3 +- gyp/gpu.gyp | 4 +- samplecode/SampleHairCurves.cpp | 112 ++++++ 18 files changed, 1082 insertions(+), 47 deletions(-) create mode 100644 gpu/src/GrAAHairLinePathRenderer.cpp create mode 100644 gpu/src/GrAAHairLinePathRenderer.h create mode 100644 gpu/src/GrAddPathRenderers_aahairline.cpp create mode 100644 samplecode/SampleHairCurves.cpp diff --git a/gpu/src/GrAAHairLinePathRenderer.cpp b/gpu/src/GrAAHairLinePathRenderer.cpp new file mode 100644 index 0000000000..40722b8eb2 --- /dev/null +++ b/gpu/src/GrAAHairLinePathRenderer.cpp @@ -0,0 +1,633 @@ +#include "GrAAHairLinePathRenderer.h" + +#include "GrContext.h" +#include "GrGpu.h" +#include "GrIndexBuffer.h" +#include "SkGeometry.h" +#include "SkTemplates.h" + +namespace { +// quadratics are rendered as 5-sided polys in order to bound the +// AA stroke around the center-curve. See comments in push_quad_index_buffer and +// bloat_quad. +static const int kVertsPerQuad = 5; +static const int kIdxsPerQuad = 9; + +static const int kVertsPerLineSeg = 4; +static const int kIdxsPerLineSeg = 6; + +static const int kNumQuadsInIdxBuffer = 256; +static const size_t kQuadIdxSBufize = kIdxsPerQuad * + sizeof(uint16_t) * + kNumQuadsInIdxBuffer; + +bool push_quad_index_data(GrIndexBuffer* qIdxBuffer) { + uint16_t* data = (uint16_t*) qIdxBuffer->lock(); + bool tempData = NULL == data; + if (tempData) { + data = new uint16_t[kNumQuadsInIdxBuffer * kIdxsPerQuad]; + } + for (int i = 0; i < kNumQuadsInIdxBuffer; ++i) { + + // Each quadratic is rendered as a five sided polygon. This poly bounds + // the quadratic's bounding triangle but has been expanded so that the + // 1-pixel wide area around the curve is inside the poly. + // If a,b,c are the original control points then the poly a0,b0,c0,c1,a1 + // that is rendered would look like this: + // b0 + // b + // + // a0 c0 + // a c + // a1 c1 + // Each is drawn as three triagnles specified by these 9 indices: + int baseIdx = i * kIdxsPerQuad; + uint16_t baseVert = (uint16_t)(i * kVertsPerQuad); + data[0 + baseIdx] = baseVert + 0; // a0 + data[1 + baseIdx] = baseVert + 1; // a1 + data[2 + baseIdx] = baseVert + 2; // b0 + data[3 + baseIdx] = baseVert + 2; // b0 + data[4 + baseIdx] = baseVert + 4; // c1 + data[5 + baseIdx] = baseVert + 3; // c0 + data[6 + baseIdx] = baseVert + 1; // a1 + data[7 + baseIdx] = baseVert + 4; // c1 + data[8 + baseIdx] = baseVert + 2; // b0 + } + if (tempData) { + bool ret = qIdxBuffer->updateData(data, kQuadIdxSBufize); + delete[] data; + return ret; + } else { + qIdxBuffer->unlock(); + return true; + } +} +} + +GrPathRenderer* GrAAHairLinePathRenderer::Create(GrContext* context) { + if (CanBeUsed(context)) { + const GrIndexBuffer* lIdxBuffer = context->getQuadIndexBuffer(); + if (NULL == lIdxBuffer) { + return NULL; + } + GrGpu* gpu = context->getGpu(); + GrIndexBuffer* qIdxBuf = gpu->createIndexBuffer(kQuadIdxSBufize, false); + SkAutoTUnref qIdxBuffer(qIdxBuf); // cons will take a ref + if (NULL == qIdxBuf || + !push_quad_index_data(qIdxBuffer.get())) { + return NULL; + } + return new GrAAHairLinePathRenderer(context, + lIdxBuffer, + qIdxBuf); + } else { + return NULL; + } +} + +bool GrAAHairLinePathRenderer::CanBeUsed(const GrContext* context) { + return context->getGpu()->supportsShaderDerivatives(); + +} + +GrAAHairLinePathRenderer::GrAAHairLinePathRenderer( + const GrContext* context, + const GrIndexBuffer* linesIndexBuffer, + const GrIndexBuffer* quadsIndexBuffer) { + GrAssert(CanBeUsed(context)); + fLinesIndexBuffer = linesIndexBuffer; + linesIndexBuffer->ref(); + fQuadsIndexBuffer = quadsIndexBuffer; + quadsIndexBuffer->ref(); + this->resetGeom(); +} + +GrAAHairLinePathRenderer::~GrAAHairLinePathRenderer() { + fLinesIndexBuffer->unref(); + fQuadsIndexBuffer->unref(); +} + +bool GrAAHairLinePathRenderer::supportsAA(GrDrawTarget* target, + const SkPath& path, + GrPathFill fill) { + return kHairLine_PathFill == fill; +} + +bool GrAAHairLinePathRenderer::canDrawPath(const GrDrawTarget* target, + const SkPath& path, + GrPathFill fill) const { + // TODO: support perspective + return kHairLine_PathFill == fill && + !target->getViewMatrix().hasPerspective(); +} + +void GrAAHairLinePathRenderer::pathWillClear() { + this->resetGeom(); +} + +void GrAAHairLinePathRenderer::resetGeom() { + fPreviousStages = ~0; + fPreviousRTHeight = ~0; + fPreviousViewMatrix = GrMatrix::InvalidMatrix(); + fLineSegmentCnt = 0; + fQuadCnt = 0; + if ((fQuadCnt || fLineSegmentCnt) && NULL != fTarget) { + fTarget->resetVertexSource(); + } +} + +namespace { + +typedef GrTArray PtArray; +typedef GrTArray IntArray; + +/** + * We convert cubics to quadratics (for now). + */ +void convert_noninflect_cubic_to_quads(const SkPoint p[4], + PtArray* quads, + int sublevel = 0) { + SkVector ab = p[1]; + ab -= p[0]; + SkVector dc = p[2]; + dc -= p[3]; + + static const SkScalar gLengthScale = 3 * SK_Scalar1 / 2; + static const SkScalar gDistanceSqdTol = 2 * SK_Scalar1; + static const int kMaxSubdivs = 30; + + ab.scale(gLengthScale); + dc.scale(gLengthScale); + + SkVector c0 = p[0]; + c0 += ab; + SkVector c1 = p[3]; + c1 += dc; + + SkScalar dSqd = c0.distanceToSqd(c1); + if (sublevel > kMaxSubdivs || dSqd <= gDistanceSqdTol) { + SkPoint cAvg = c0; + cAvg += c1; + cAvg.scale(SK_ScalarHalf); + + int idx = quads->count(); + quads->push_back_n(3); + (*quads)[idx+0] = p[0]; + (*quads)[idx+1] = cAvg; + (*quads)[idx+2] = p[3]; + + return; + } 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); + } +} + +void convert_cubic_to_quads(const SkPoint p[4], 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); + } +} + +// Takes 178th time of logf on Z600 / VC2010 +int get_float_exp(float x) { + GR_STATIC_ASSERT(sizeof(int) == sizeof(float)); +#if GR_DEBUG + static bool tested; + if (!tested) { + tested = true; + GrAssert(get_float_exp(0.25f) == -2); + GrAssert(get_float_exp(0.3f) == -2); + GrAssert(get_float_exp(0.5f) == -1); + GrAssert(get_float_exp(1.f) == 0); + GrAssert(get_float_exp(2.f) == 1); + GrAssert(get_float_exp(2.5f) == 1); + GrAssert(get_float_exp(8.f) == 3); + GrAssert(get_float_exp(100.f) == 6); + GrAssert(get_float_exp(1000.f) == 9); + GrAssert(get_float_exp(1024.f) == 10); + GrAssert(get_float_exp(3000000.f) == 21); + } +#endif + return (((*(int*)&x) & 0x7f800000) >> 23) - 127; +} + +// we subdivide the quads to avoid huge overfill +// if it returns -1 then should be drawn as lines +int num_quad_subdivs(const SkPoint p[3]) { + static const SkScalar gDegenerateToLineTol = SK_Scalar1; + + GrScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]); + if (dsqd < gDegenerateToLineTol*gDegenerateToLineTol) { + return -1; + } + if (p[2].distanceToLineBetweenSqd(p[1],p[0]) < + gDegenerateToLineTol*gDegenerateToLineTol) { + return -1; + } + + static const int kMaxSub = 4; + // tolerance of triangle height in pixels + // tuned on windows Quadro FX 380 / Z600 + // trade off of fill vs cpu time on verts + // maybe different when do this using gpu (geo or tess shaders) + static const SkScalar gSubdivTol = 175 * SK_Scalar1; + + if (dsqd <= gSubdivTol*gSubdivTol) { + return 0; + } else { + // subdividing the quad reduces d by 4. so we want x = log4(d/tol) + // = log4(d*d/tol*tol)/2 + // = log2(d*d/tol*tol) + +#ifdef SK_SCALAR_IS_FLOAT + // +1 since we're ignoring the mantissa contribution. + int log = get_float_exp(dsqd/(gSubdivTol*gSubdivTol)) + 1; + log = GrMin(GrMax(0, log), kMaxSub); + return log; +#else + SkScalar log = SkScalarLog(SkScalarDiv(dsqd,kTol*kTol)); + static const SkScalar conv = SkScalarInvert(SkScalarLog(2)); + log = SkScalarMul(log, conv); + return GrMin(GrMax(0, SkScalarCeilToInt(log)),kMaxSub); +#endif + } +} + +int get_lines_and_quads(const SkPath& path, const SkMatrix& m, GrIRect clip, + PtArray* lines, PtArray* quads, + IntArray* quadSubdivCnts) { + SkPath::Iter iter(path, false); + + int totalQuadCount = 0; + GrRect bounds; + GrIRect ibounds; + for (;;) { + GrPoint pts[4]; + GrPathCmd cmd = (GrPathCmd)iter.next(pts); + switch (cmd) { + case kMove_PathCmd: + break; + case kLine_PathCmd: + m.mapPoints(pts,2); + bounds.setBounds(pts, 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]; + } + break; + case kQuadratic_PathCmd: { + bounds.setBounds(pts, 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); + 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]; + } else { + quads->push_back() = pts[0]; + quads->push_back() = pts[1]; + quads->push_back() = pts[2]; + quadSubdivCnts->push_back() = subdiv; + totalQuadCount += 1 << subdiv; + } + } + } break; + case kCubic_PathCmd: { + bounds.setBounds(pts, 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); + for (int i = 0; i < q.count(); i += 3) { + bounds.setBounds(&q[i], 3); + bounds.outset(SK_Scalar1, SK_Scalar1); + bounds.roundOut(&ibounds); + if (SkIRect::Intersects(clip, ibounds)) { + int subdiv = num_quad_subdivs(&q[i]); + 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]; + } else { + quads->push_back() = q[0 + i]; + quads->push_back() = q[1 + i]; + quads->push_back() = q[2 + i]; + quadSubdivCnts->push_back() = subdiv; + totalQuadCount += 1 << subdiv; + } + } + } + } + } break; + case kClose_PathCmd: + break; + case kEnd_PathCmd: + return totalQuadCount; + } + } +} + +struct Vertex { + GrPoint fPos; + union { + struct { + GrScalar fA; + GrScalar fB; + GrScalar fC; + } fLine; + GrVec fQuadCoord; + struct { + GrScalar fBogus[4]; + }; + }; +}; +GR_STATIC_ASSERT(sizeof(Vertex) == 3 * sizeof(GrPoint)); + +void intersect_lines(const SkPoint& ptA, const SkVector& normA, + const SkPoint& ptB, const SkVector& normB, + SkPoint* result) { + + SkScalar lineAW = -normA.dot(ptA); + SkScalar lineBW = -normB.dot(ptB); + + SkScalar wInv = SkScalarMul(normA.fX, normB.fY) - + SkScalarMul(normA.fY, normB.fX); + wInv = SkScalarInvert(wInv); + + result->fX = SkScalarMul(normA.fY, lineBW) - SkScalarMul(lineAW, normB.fY); + result->fX = SkScalarMul(result->fX, wInv); + + result->fY = SkScalarMul(lineAW, normB.fX) - SkScalarMul(normA.fX, lineBW); + result->fY = SkScalarMul(result->fY, wInv); +} + +void bloat_quad(const SkPoint qpts[3], Vertex verts[kVertsPerQuad]) { + // 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]; + + // compute a matrix that goes from device coords to U,V quad params + SkMatrix DevToUV; + DevToUV.setAll(a.fX, b.fX, c.fX, + a.fY, b.fY, c.fY, + SK_Scalar1, SK_Scalar1, SK_Scalar1); + DevToUV.invert(&DevToUV); + // can't make this static, no cons :( + SkMatrix UVpts; + UVpts.setAll(0, SK_ScalarHalf, SK_Scalar1, + 0, 0, SK_Scalar1, + SK_Scalar1, SK_Scalar1, SK_Scalar1); + DevToUV.postConcat(UVpts); + + // We really want to avoid perspective matrix muls. + // These may wind up really close to zero + DevToUV.setPerspX(0); + DevToUV.setPerspY(0); + + SkVector ab = b; + ab -= a; + SkVector ac = c; + ac -= a; + SkVector cb = b; + cb -= c; + + // We should have already handled degenerates + GrAssert(ab.length() > 0 && cb.length() > 0); + + ab.normalize(); + SkVector abN; + abN.setOrthog(ab, SkVector::kLeft_Side); + if (abN.dot(ac) > 0) { + abN.negate(); + } + + cb.normalize(); + SkVector cbN; + cbN.setOrthog(cb, SkVector::kLeft_Side); + if (cbN.dot(ac) < 0) { + cbN.negate(); + } + + a0.fPos = a; + a0.fPos += abN; + a1.fPos = a; + a1.fPos -= abN; + + c0.fPos = c; + c0.fPos += cbN; + c1.fPos = c; + c1.fPos -= cbN; + + intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos); + + DevToUV.mapPointsWithStride(&verts[0].fQuadCoord, + &verts[0].fPos, sizeof(Vertex), kVertsPerQuad); +} + +void add_quads(const SkPoint p[3], + int subdiv, + 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); + } else { + bloat_quad(p, *vert); + *vert += kVertsPerQuad; + } +} + +void add_line(const SkPoint p[2], + int rtHeight, + Vertex** vert) { + const SkPoint& a = p[0]; + const SkPoint& b = p[1]; + + SkVector orthVec = b; + orthVec -= a; + + if (orthVec.setLength(SK_Scalar1)) { + orthVec.setOrthog(orthVec); + + // the values we pass down to the frag shader + // have to be in y-points-up space; + SkVector normal; + normal.fX = orthVec.fX; + normal.fY = -orthVec.fY; + SkPoint aYDown; + aYDown.fX = a.fX; + aYDown.fY = rtHeight - a.fY; + + SkScalar lineC = -(aYDown.dot(normal)); + for (int i = 0; i < kVertsPerLineSeg; ++i) { + (*vert)[i].fPos = (i < 2) ? a : b; + if (0 == i || 3 == i) { + (*vert)[i].fPos -= orthVec; + } else { + (*vert)[i].fPos += orthVec; + } + (*vert)[i].fLine.fA = normal.fX; + (*vert)[i].fLine.fB = normal.fY; + (*vert)[i].fLine.fC = lineC; + } + } else { + // just make it degenerate and likely offscreen + (*vert)[0].fPos.set(SK_ScalarMax, SK_ScalarMax); + (*vert)[1].fPos.set(SK_ScalarMax, SK_ScalarMax); + (*vert)[2].fPos.set(SK_ScalarMax, SK_ScalarMax); + (*vert)[3].fPos.set(SK_ScalarMax, SK_ScalarMax); + } + + *vert += kVertsPerLineSeg; +} + +} + +bool GrAAHairLinePathRenderer::createGeom(GrDrawTarget::StageBitfield stages) { + + int rtHeight = fTarget->getRenderTarget()->height(); + + GrIRect clip; + if (fTarget->getClip().hasConservativeBounds()) { + GrRect clipRect = fTarget->getClip().getConservativeBounds(); + clipRect.roundOut(&clip); + } else { + clip.setLargest(); + } + + if (stages == fPreviousStages && + fPreviousViewMatrix == fTarget->getViewMatrix() && + rtHeight == fPreviousRTHeight && + fClipRect == clip) { + return true; + } + + GrVertexLayout layout = GrDrawTarget::kEdge_VertexLayoutBit; + for (int s = 0; s < GrDrawTarget::kNumStages; ++s) { + if ((1 << s) & stages) { + layout |= GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(s); + } + } + + 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); + + fLineSegmentCnt = lines.count() / 2; + int vertCnt = kVertsPerLineSeg * fLineSegmentCnt + kVertsPerQuad * fQuadCnt; + + GrAssert(sizeof(Vertex) == GrDrawTarget::VertexSize(layout)); + + Vertex* verts; + if (!fTarget->reserveVertexSpace(layout, vertCnt, (void**)&verts)) { + return false; + } + + for (int i = 0; i < fLineSegmentCnt; ++i) { + add_line(&lines[2*i], rtHeight, &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); + } + + fPreviousStages = stages; + fPreviousViewMatrix = fTarget->getViewMatrix(); + fPreviousRTHeight = rtHeight; + fClipRect = clip; + 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); + } + + fTarget->setViewMatrix(GrMatrix::I()); + + // TODO: See whether rendering lines as degenerate quads improves perf + // when we have a mix + fTarget->setIndexSourceToBuffer(fLinesIndexBuffer); + int lines = 0; + int nBufLines = fLinesIndexBuffer->maxQuads(); + while (lines < fLineSegmentCnt) { + int n = GrMin(fLineSegmentCnt-lines, nBufLines); + fTarget->setVertexEdgeType(GrDrawTarget::kHairLine_EdgeType); + fTarget->drawIndexed(kTriangles_PrimitiveType, + kVertsPerLineSeg*lines, // startV + 0, // startI + kVertsPerLineSeg*n, // vCount + kIdxsPerLineSeg*n); // iCount + lines += n; + } + + fTarget->setIndexSourceToBuffer(fQuadsIndexBuffer); + int quads = 0; + while (quads < fQuadCnt) { + int n = GrMin(fQuadCnt-quads, kNumQuadsInIdxBuffer); + fTarget->setVertexEdgeType(GrDrawTarget::kHairQuad_EdgeType); + fTarget->drawIndexed(kTriangles_PrimitiveType, + 4*fLineSegmentCnt + kVertsPerQuad*quads, // startV + 0, // startI + kVertsPerQuad*n, // vCount + kIdxsPerQuad*n); // iCount + quads += n; + } + +} + diff --git a/gpu/src/GrAAHairLinePathRenderer.h b/gpu/src/GrAAHairLinePathRenderer.h new file mode 100644 index 0000000000..c7d2dc722a --- /dev/null +++ b/gpu/src/GrAAHairLinePathRenderer.h @@ -0,0 +1,62 @@ + +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrAAHairLinePathRenderer_DEFINED +#define GrAAHairLinePathRenderer_DEFINED + +#include "GrPathRenderer.h" + +class GrAAHairLinePathRenderer : public GrPathRenderer { +public: + virtual ~GrAAHairLinePathRenderer(); + + static GrPathRenderer* Create(GrContext* context); + // GrPathRenderer overrides + virtual bool supportsAA(GrDrawTarget* target, + const SkPath& path, + GrPathFill fill); + virtual bool canDrawPath(const GrDrawTarget* target, + const SkPath& path, + GrPathFill fill) const; + virtual void drawPath(GrDrawTarget::StageBitfield stages); + +protected: + + // GrPathRenderer overrides + virtual void pathWillClear(); + +private: + void resetGeom(); + + static bool CanBeUsed(const GrContext* context); + GrAAHairLinePathRenderer(const GrContext* context, + const GrIndexBuffer* fLinesIndexBuffer, + const GrIndexBuffer* fQuadsIndexBuffer); + + bool createGeom(GrDrawTarget::StageBitfield stages); + + GrContext* fContext; + const GrIndexBuffer* fLinesIndexBuffer; + const GrIndexBuffer* fQuadsIndexBuffer; + + // have to recreate geometry if stages in use changes :( + GrDrawTarget::StageBitfield fPreviousStages; + int fPreviousRTHeight; + GrIRect fClipRect; + + // this path renderer draws everything in device coordinates + GrMatrix fPreviousViewMatrix; + int fLineSegmentCnt; + int fQuadCnt; + + typedef GrPathRenderer INHERITED; +}; + + +#endif + diff --git a/gpu/src/GrAddPathRenderers_aahairline.cpp b/gpu/src/GrAddPathRenderers_aahairline.cpp new file mode 100644 index 0000000000..a7df66e980 --- /dev/null +++ b/gpu/src/GrAddPathRenderers_aahairline.cpp @@ -0,0 +1,20 @@ + +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "GrAAHairLinePathRenderer.h" + +void GrPathRenderer::AddPathRenderers(GrContext* ctx, + GrPathRendererChain::UsageFlags flags, + GrPathRendererChain* chain) { + if (!(GrPathRendererChain::kNonAAOnly_UsageFlag & flags)) { + if (GrPathRenderer* pr = GrAAHairLinePathRenderer::Create(ctx)) { + chain->addPathRenderer(pr)->unref(); + } + } +} diff --git a/gpu/src/GrContext.cpp b/gpu/src/GrContext.cpp index 6692023a73..a97c857490 100644 --- a/gpu/src/GrContext.cpp +++ b/gpu/src/GrContext.cpp @@ -1176,7 +1176,7 @@ void GrContext::drawRect(const GrPaint& paint, GrRect devRect = rect; GrMatrix combinedMatrix; - bool doAA = apply_aa_to_rect(target, paint, rect, width, matrix, + bool doAA = apply_aa_to_rect(target, paint, rect, width, matrix, &combinedMatrix, &devRect); if (doAA) { @@ -1378,9 +1378,12 @@ void GrContext::drawVertices(const GrPaint& paint, } int texOffsets[GrDrawTarget::kMaxTexCoords]; int colorOffset; + int edgeOffset; GrDrawTarget::VertexSizeAndOffsetsByIdx(layout, texOffsets, - &colorOffset); + &colorOffset, + &edgeOffset); + GrAssert(-1 == edgeOffset); void* curVertex = geo.vertices(); for (int i = 0; i < vertexCount; ++i) { diff --git a/gpu/src/GrDefaultPathRenderer.h b/gpu/src/GrDefaultPathRenderer.h index e11716ed61..dd5964122d 100644 --- a/gpu/src/GrDefaultPathRenderer.h +++ b/gpu/src/GrDefaultPathRenderer.h @@ -20,7 +20,8 @@ public: GrDefaultPathRenderer(bool separateStencilSupport, bool stencilWrapOpsSupport); - virtual bool canDrawPath(const SkPath& path, + virtual bool canDrawPath(const GrDrawTarget* target, + const SkPath& path, GrPathFill fill) const { return true; } virtual bool requiresStencilPass(const GrDrawTarget* target, diff --git a/gpu/src/GrDrawTarget.cpp b/gpu/src/GrDrawTarget.cpp index 29a37a469e..51cff6dec3 100644 --- a/gpu/src/GrDrawTarget.cpp +++ b/gpu/src/GrDrawTarget.cpp @@ -63,6 +63,17 @@ bool check_layout(GrVertexLayout layout) { return true; } +int num_tex_coords(GrVertexLayout layout) { + int cnt = 0; + // figure out how many tex coordinates are present + for (int t = 0; t < GrDrawTarget::kMaxTexCoords; ++t) { + if (tex_coord_idx_mask(t) & layout) { + ++cnt; + } + } + return cnt; +} + } //unnamed namespace size_t GrDrawTarget::VertexSize(GrVertexLayout vertexLayout) { @@ -73,14 +84,13 @@ size_t GrDrawTarget::VertexSize(GrVertexLayout vertexLayout) { sizeof(GrPoint); size_t size = vecSize; // position - for (int t = 0; t < kMaxTexCoords; ++t) { - if (tex_coord_idx_mask(t) & vertexLayout) { - size += vecSize; - } - } + size += num_tex_coords(vertexLayout) * vecSize; if (vertexLayout & kColor_VertexLayoutBit) { size += sizeof(GrColor); } + if (vertexLayout & kEdge_VertexLayoutBit) { + size += 4 * sizeof(GrScalar); + } return size; } @@ -111,16 +121,27 @@ int GrDrawTarget::VertexStageCoordOffset(int stage, GrVertexLayout vertexLayout) int GrDrawTarget::VertexColorOffset(GrVertexLayout vertexLayout) { GrAssert(check_layout(vertexLayout)); + // color is after the pos and tex coords if (vertexLayout & kColor_VertexLayoutBit) { int vecSize = (vertexLayout & kTextFormat_VertexLayoutBit) ? sizeof(GrGpuTextVertex) : sizeof(GrPoint); - int offset = vecSize; // position - // figure out how many tex coordinates are present and precede this one. - for (int t = 0; t < kMaxTexCoords; ++t) { - if (tex_coord_idx_mask(t) & vertexLayout) { - offset += vecSize; - } + return vecSize * (num_tex_coords(vertexLayout) + 1); //+1 for pos + } + return -1; +} + +int GrDrawTarget::VertexEdgeOffset(GrVertexLayout vertexLayout) { + GrAssert(check_layout(vertexLayout)); + + // edge pts are after the pos, tex coords, and color + if (vertexLayout & kEdge_VertexLayoutBit) { + int vecSize = (vertexLayout & kTextFormat_VertexLayoutBit) ? + sizeof(GrGpuTextVertex) : + sizeof(GrPoint); + int offset = vecSize * (num_tex_coords(vertexLayout) + 1); //+1 for pos + if (vertexLayout & kColor_VertexLayoutBit) { + offset += sizeof(GrColor); } return offset; } @@ -129,11 +150,13 @@ int GrDrawTarget::VertexColorOffset(GrVertexLayout vertexLayout) { int GrDrawTarget::VertexSizeAndOffsetsByIdx(GrVertexLayout vertexLayout, int texCoordOffsetsByIdx[kMaxTexCoords], - int* colorOffset) { + int* colorOffset, + int* edgeOffset) { GrAssert(check_layout(vertexLayout)); GrAssert(NULL != texCoordOffsetsByIdx); GrAssert(NULL != colorOffset); + GrAssert(NULL != edgeOffset); int vecSize = (vertexLayout & kTextFormat_VertexLayoutBit) ? sizeof(GrGpuTextVertex) : @@ -154,21 +177,30 @@ int GrDrawTarget::VertexSizeAndOffsetsByIdx(GrVertexLayout vertexLayout, } else { *colorOffset = -1; } + if (kEdge_VertexLayoutBit & vertexLayout) { + *edgeOffset = size; + size += 4 * sizeof(GrScalar); + } else { + *edgeOffset = -1; + } return size; } int GrDrawTarget::VertexSizeAndOffsetsByStage(GrVertexLayout vertexLayout, int texCoordOffsetsByStage[kNumStages], - int* colorOffset) { + int* colorOffset, + int* edgeOffset) { GrAssert(check_layout(vertexLayout)); GrAssert(NULL != texCoordOffsetsByStage); GrAssert(NULL != colorOffset); + GrAssert(NULL != edgeOffset); int texCoordOffsetsByIdx[kMaxTexCoords]; int size = VertexSizeAndOffsetsByIdx(vertexLayout, texCoordOffsetsByIdx, - colorOffset); + colorOffset, + edgeOffset); for (int s = 0; s < kNumStages; ++s) { int tcIdx; if (StagePosAsTexCoordVertexLayoutBit(s) & vertexLayout) { @@ -250,22 +282,39 @@ void GrDrawTarget::VertexLayoutUnitTest() { GrAssert(VertexUsesStage(s2, posAsTex)); GrAssert(2*sizeof(GrPoint) == VertexSize(posAsTex)); GrAssert(-1 == VertexTexCoordsForStage(s2, posAsTex)); + GrAssert(-1 == VertexEdgeOffset(posAsTex)); } + GrAssert(-1 == VertexEdgeOffset(tcMask)); + GrAssert(-1 == VertexColorOffset(tcMask)); #if GR_DEBUG GrVertexLayout withColor = tcMask | kColor_VertexLayoutBit; #endif GrAssert(2*sizeof(GrPoint) == VertexColorOffset(withColor)); GrAssert(2*sizeof(GrPoint) + sizeof(GrColor) == VertexSize(withColor)); + #if GR_DEBUG + GrVertexLayout withEdge = tcMask | kEdge_VertexLayoutBit; + #endif + GrAssert(-1 == VertexColorOffset(withEdge)); + GrAssert(2*sizeof(GrPoint) == VertexEdgeOffset(withEdge)); + GrAssert(4*sizeof(GrPoint) == VertexSize(withEdge)); + #if GR_DEBUG + GrVertexLayout withColorAndEdge = withColor | kEdge_VertexLayoutBit; + #endif + GrAssert(2*sizeof(GrPoint) == VertexColorOffset(withColorAndEdge)); + GrAssert(2*sizeof(GrPoint) + sizeof(GrColor) == VertexEdgeOffset(withColorAndEdge)); + GrAssert(4*sizeof(GrPoint) + sizeof(GrColor) == VertexSize(withColorAndEdge)); } GrAssert(tex_coord_idx_mask(t) == tcMask); GrAssert(check_layout(tcMask)); int stageOffsets[kNumStages]; int colorOffset; + int edgeOffset; int size; - size = VertexSizeAndOffsetsByStage(tcMask, stageOffsets, &colorOffset); + size = VertexSizeAndOffsetsByStage(tcMask, stageOffsets, &colorOffset, &edgeOffset); GrAssert(2*sizeof(GrPoint) == size); GrAssert(-1 == colorOffset); + GrAssert(-1 == edgeOffset); for (int s = 0; s < kNumStages; ++s) { GrAssert(VertexUsesStage(s, tcMask)); GrAssert(sizeof(GrPoint) == stageOffsets[s]); @@ -698,7 +747,8 @@ void GrDrawTarget::drawNonIndexed(GrPrimitiveType type, bool GrDrawTarget::CanDisableBlend(GrVertexLayout layout, const DrState& state) { // If we compute a coverage value (using edge AA or a coverage stage) then // we can't force blending off. - if (state.fEdgeAANumEdges > 0) { + if (state.fEdgeAANumEdges > 0 || + layout & kEdge_VertexLayoutBit) { return false; } for (int s = state.fFirstCoverageStage; s < kNumStages; ++s) { @@ -837,8 +887,11 @@ void GrDrawTarget::SetRectVertices(const GrRect& rect, int stageOffsets[kNumStages]; int colorOffset; - int vsize = VertexSizeAndOffsetsByStage(layout, stageOffsets, &colorOffset); + int edgeOffset; + int vsize = VertexSizeAndOffsetsByStage(layout, stageOffsets, + &colorOffset, &edgeOffset); GrAssert(-1 == colorOffset); + GrAssert(-1 == edgeOffset); GrTCast(vertices)->setRectFan(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, diff --git a/gpu/src/GrDrawTarget.h b/gpu/src/GrDrawTarget.h index f170fda10a..42723f6da3 100644 --- a/gpu/src/GrDrawTarget.h +++ b/gpu/src/GrDrawTarget.h @@ -59,6 +59,20 @@ public: kMaxEdges = 32 }; + /** + * 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. + */ + enum VertexEdgeType { + /* 1-pixel wide line + 2D implicit line eq (a*x + b*y +c = 0). 4th component unused */ + kHairLine_EdgeType, + /* 1-pixel wide quadratic + u^2-v canonical coords (only 2 components used) */ + kHairQuad_EdgeType + }; + /** * Bitfield used to indicate which stages are in use. */ @@ -167,6 +181,7 @@ protected: GrStencilSettings fStencilSettings; GrMatrix fViewMatrix; + VertexEdgeType fVertexEdgeType; Edge fEdgeAAEdges[kMaxEdges]; int fEdgeAANumEdges; bool operator ==(const DrState& s) const { @@ -256,7 +271,7 @@ public: } /** - * Shortcut for preConcatSamplerMatrix on all stages in mask with same + * Shortcut for preConcatSamplerMatrix on all stages in mask with same * matrix */ void preConcatSamplerMatrices(int stageMask, const GrMatrix& matrix) { @@ -267,6 +282,18 @@ public: } } + /** + * Shortcut for preConcatSamplerMatrix on all enabled stages in mask with + * same matrix + * + * @param stage the stage of the sampler to set + * @param matrix the matrix to concat + */ + void preConcatEnabledSamplerMatrices(const GrMatrix& matrix) { + StageBitfield stageMask = this->enabledStages(); + this->preConcatSamplerMatrices(stageMask, matrix); + } + /** * Gets the matrix of a stage's sampler * @@ -535,6 +562,15 @@ public: */ bool canDisableBlend() const; + /** + * Determines the interpretation per-vertex edge data when the + * kEdge_VertexLayoutBit is set (see below). When per-vertex edges are not + * specified the value of this setting has no effect. + */ + void setVertexEdgeType(VertexEdgeType type) { + fCurrDrawState.fVertexEdgeType = type; + } + /** * Given the current draw state, vertex layout, and hw support, will HW AA * lines be used (if line primitive type is drawn)? (Note that lines are @@ -575,15 +611,14 @@ public: * Additional Bits that can be specified in GrVertexLayout. */ enum VertexLayoutBits { - + /* vertices have colors */ kColor_VertexLayoutBit = 1 << (STAGE_BIT_CNT + 0), - //isStageEnabled(s) ? 1 : 0; + } + return mask; + } + // Helpers for GrDrawTarget subclasses that won't have private access to // SavedDrawState but need to peek at the state values. static DrState& accessSavedDrawState(SavedDrawState& sds) diff --git a/gpu/src/GrGLProgram.cpp b/gpu/src/GrGLProgram.cpp index ed3cd120f6..bfe6629135 100644 --- a/gpu/src/GrGLProgram.cpp +++ b/gpu/src/GrGLProgram.cpp @@ -44,6 +44,7 @@ const char* GrShaderPrecision(const GrGLInterface* gl) { #define POS_ATTR_NAME "aPosition" #define COL_ATTR_NAME "aColor" +#define EDGE_ATTR_NAME "aEdge" #define COL_UNI_NAME "uColor" #define EDGES_UNI_NAME "uEdges" #define COL_FILTER_UNI_NAME "uColorFilter" @@ -539,6 +540,25 @@ bool GrGLProgram::genProgram(const GrGLInterface* gl, segments.fFSCode.append(";\n"); } inCoverage = "edgeAlpha"; + } else if (layout & GrDrawTarget::kEdge_VertexLayoutBit) { + segments.fVSAttrs.append("attribute vec4 " EDGE_ATTR_NAME ";\n"); + segments.fVaryings.append("varying vec4 vEdge;\n"); + segments.fVSCode.append("\tvEdge = " EDGE_ATTR_NAME ";\n"); + if (GrDrawTarget::kHairLine_EdgeType == fProgramDesc.fVertexEdgeType) { + segments.fFSCode.append("\tfloat edgeAlpha = abs(dot(vec3(gl_FragCoord.xy,1), vEdge.xyz));\n"); + } else { + GrAssert(GrDrawTarget::kHairQuad_EdgeType == fProgramDesc.fVertexEdgeType); + // for now we know we're not in perspective, so we could compute this + // per-quadratic rather than per pixel + segments.fFSCode.append("\tvec2 duvdx = dFdx(vEdge.xy);\n"); + segments.fFSCode.append("\tvec2 duvdy = dFdy(vEdge.xy);\n"); + segments.fFSCode.append("\tfloat dfdx = 2.0*vEdge.x*duvdx.x - duvdx.y;\n"); + segments.fFSCode.append("\tfloat dfdy = 2.0*vEdge.x*duvdy.x - duvdy.y;\n"); + segments.fFSCode.append("\tfloat edgeAlpha = (vEdge.x*vEdge.x - vEdge.y);\n"); + segments.fFSCode.append("\tedgeAlpha = sqrt(edgeAlpha*edgeAlpha / (dfdx*dfdx + dfdy*dfdy));\n"); + } + segments.fFSCode.append("\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n"); + inCoverage = "edgeAlpha"; } GrStringBuilder outCoverage; @@ -839,8 +859,10 @@ bool GrGLProgram::bindOutputsAttribsAndLinkProgram( } } - GR_GL_CALL(gl, BindAttribLocation(progID, ColorAttributeIdx(), + GR_GL_CALL(gl, BindAttribLocation(progID, ColorAttributeIdx(), COL_ATTR_NAME)); + GR_GL_CALL(gl, BindAttribLocation(progID, EdgeAttributeIdx(), + EDGE_ATTR_NAME)); GR_GL_CALL(gl, LinkProgram(progID)); diff --git a/gpu/src/GrGLProgram.h b/gpu/src/GrGLProgram.h index b89653fb81..78d25b5f9d 100644 --- a/gpu/src/GrGLProgram.h +++ b/gpu/src/GrGLProgram.h @@ -65,6 +65,8 @@ public: static int PositionAttributeIdx() { return 0; } static int TexCoordAttributeIdx(int tcIdx) { return 1 + tcIdx; } static int ColorAttributeIdx() { return 1 + GrDrawTarget::kMaxTexCoords; } + static int EdgeAttributeIdx() { return 2 + GrDrawTarget::kMaxTexCoords; } + static int ViewMatrixAttributeIdx() { return 2 + GrDrawTarget::kMaxTexCoords; } @@ -150,6 +152,8 @@ private: kDualSrcOutputCnt }; + GrDrawTarget::VertexEdgeType fVertexEdgeType; + // stripped of bits that don't affect prog generation GrVertexLayout fVertexLayout; diff --git a/gpu/src/GrGpuGLFixed.cpp b/gpu/src/GrGpuGLFixed.cpp index 69bf94f533..2c97e69ce2 100644 --- a/gpu/src/GrGpuGLFixed.cpp +++ b/gpu/src/GrGpuGLFixed.cpp @@ -276,15 +276,22 @@ void GrGpuGLFixed::setupGeometry(int* startVertex, int newColorOffset; int newTexCoordOffsets[kNumStages]; + int newEdgeOffset; GrGLsizei newStride = VertexSizeAndOffsetsByStage(this->getGeomSrc().fVertexLayout, newTexCoordOffsets, - &newColorOffset); + &newColorOffset, + &newEdgeOffset); + GrAssert(-1 == newEdgeOffset); // not supported by fixed pipe + int oldColorOffset; int oldTexCoordOffsets[kNumStages]; + int oldEdgeOffset; GrGLsizei oldStride = VertexSizeAndOffsetsByStage(fHWGeometryState.fVertexLayout, oldTexCoordOffsets, - &oldColorOffset); + &oldColorOffset, + &oldEdgeOffset); + GrAssert(-1 == oldEdgeOffset); bool indexed = NULL != startIndex; diff --git a/gpu/src/GrGpuGLShaders.cpp b/gpu/src/GrGpuGLShaders.cpp index 05b1e88088..a9a3953673 100644 --- a/gpu/src/GrGpuGLShaders.cpp +++ b/gpu/src/GrGpuGLShaders.cpp @@ -185,11 +185,24 @@ bool GrGpuGLShaders::programUnitTest() { pdesc.fFirstCoverageStage = idx; bool edgeAA = random.nextF() > .5f; - if (edgeAA) { - pdesc.fEdgeAANumEdges = random.nextF() * this->getMaxEdges() + 1; - pdesc.fEdgeAAConcave = random.nextF() > .5f; - } else { - pdesc.fEdgeAANumEdges = 0; + if (edgeAA) { + bool vertexEdgeAA = random.nextF() > .5f; + if (vertexEdgeAA) { + pdesc.fVertexLayout |= GrDrawTarget::kEdge_VertexLayoutBit; + if (this->supportsShaderDerivatives()) { + pdesc.fVertexEdgeType = random.nextF() > 0.5f ? + kHairQuad_EdgeType : + kHairLine_EdgeType; + } else { + pdesc.fVertexEdgeType = kHairLine_EdgeType; + } + pdesc.fEdgeAANumEdges = 0; + } else { + pdesc.fEdgeAANumEdges = random.nextF() * this->getMaxEdges() + 1; + pdesc.fEdgeAAConcave = random.nextF() > .5f; + } + } else { + pdesc.fEdgeAANumEdges = 0; } if (fDualSourceBlendingSupport) { @@ -307,6 +320,7 @@ void GrGpuGLShaders::resetContext() { fHWGeometryState.fVertexLayout = 0; fHWGeometryState.fVertexOffset = ~0; GL_CALL(DisableVertexAttribArray(GrGLProgram::ColorAttributeIdx())); + GL_CALL(DisableVertexAttribArray(GrGLProgram::EdgeAttributeIdx())); for (int t = 0; t < kMaxTexCoords; ++t) { GL_CALL(DisableVertexAttribArray(GrGLProgram::TexCoordAttributeIdx(t))); } @@ -641,17 +655,22 @@ void GrGpuGLShaders::setupGeometry(int* startVertex, int newColorOffset; int newTexCoordOffsets[kMaxTexCoords]; + int newEdgeOffset; GrGLsizei newStride = VertexSizeAndOffsetsByIdx( this->getGeomSrc().fVertexLayout, newTexCoordOffsets, - &newColorOffset); + &newColorOffset, + &newEdgeOffset); int oldColorOffset; int oldTexCoordOffsets[kMaxTexCoords]; + int oldEdgeOffset; + GrGLsizei oldStride = VertexSizeAndOffsetsByIdx( fHWGeometryState.fVertexLayout, oldTexCoordOffsets, - &oldColorOffset); + &oldColorOffset, + &oldEdgeOffset); bool indexed = NULL != startIndex; int extraVertexOffset; @@ -727,6 +746,21 @@ void GrGpuGLShaders::setupGeometry(int* startVertex, GL_CALL(DisableVertexAttribArray(GrGLProgram::ColorAttributeIdx())); } + if (newEdgeOffset > 0) { + GrGLvoid* edgeOffset = (int8_t*)(vertexOffset + newEdgeOffset); + int idx = GrGLProgram::EdgeAttributeIdx(); + if (oldEdgeOffset <= 0) { + GL_CALL(EnableVertexAttribArray(idx)); + GL_CALL(VertexAttribPointer(idx, 4, scalarType, + false, newStride, edgeOffset)); + } else if (allOffsetsChange || newEdgeOffset != oldEdgeOffset) { + GL_CALL(VertexAttribPointer(idx, 4, scalarType, + false, newStride, edgeOffset)); + } + } else if (oldEdgeOffset > 0) { + GL_CALL(DisableVertexAttribArray(GrGLProgram::EdgeAttributeIdx())); + } + fHWGeometryState.fVertexLayout = this->getGeomSrc().fVertexLayout; fHWGeometryState.fArrayPtrsDirty = false; } @@ -734,6 +768,11 @@ void GrGpuGLShaders::setupGeometry(int* startVertex, void GrGpuGLShaders::buildProgram(GrPrimitiveType type) { ProgramDesc& desc = fCurrentProgram.fProgramDesc; + // The descriptor is used as a cache key. Thus when a field of the + // descriptor will not affect program generation (because of the vertex + // layout in use or other descriptor field settings) it should be set + // to a canonical value to avoid duplicate programs with different keys. + // Must initialize all fields or cache will have false negatives! desc.fVertexLayout = this->getGeomSrc().fVertexLayout; @@ -767,6 +806,13 @@ void GrGpuGLShaders::buildProgram(GrPrimitiveType type) { int lastEnabledStage = -1; + if (desc.fVertexLayout & GrDrawTarget::kEdge_VertexLayoutBit) { + desc.fVertexEdgeType = fCurrDrawState.fVertexEdgeType; + } else { + // use canonical value when not set to avoid cache misses + desc.fVertexEdgeType = GrDrawTarget::kHairLine_EdgeType; + } + for (int s = 0; s < kNumStages; ++s) { StageDesc& stage = desc.fStages[s]; diff --git a/gpu/src/GrPathRenderer.h b/gpu/src/GrPathRenderer.h index 25dfafb032..4694de5252 100644 --- a/gpu/src/GrPathRenderer.h +++ b/gpu/src/GrPathRenderer.h @@ -52,13 +52,28 @@ public: /** * Returns true if this path renderer is able to render the path. * Returning false allows the caller to fallback to another path renderer. + * When searching for a path renderer capable of rendering a path this + * function is called. The path renderer can examine the path, fill rule, + * and draw settings that will be used (via the targetparameter). If "true" + * is reported note that the caller is permitted to make modifications to + * the following settings of the target between the calls to canDrawPath and + * drawPath: + * 1. view matrix: The matrix at drawPath time may have additional scale + * scale and translation applied + * 2. render target: The render target may change between canDrawPath + * and drawPath. + * The GrPathRenderer subclass's decision about whether to return true + * or false in its implementation of this function should consider these + * possible state changes. * * @param path The path to draw * @param fill The fill rule to use * * @return true if the path can be drawn by this object, false otherwise. */ - virtual bool canDrawPath(const SkPath& path, GrPathFill fill) const = 0; + virtual bool canDrawPath(const GrDrawTarget* target, + const SkPath& path, + GrPathFill fill) const = 0; /** * For complex clips Gr uses the stencil buffer. The path renderer must be diff --git a/gpu/src/GrPathRendererChain.cpp b/gpu/src/GrPathRendererChain.cpp index 4c76720f9a..dc09d43c1e 100644 --- a/gpu/src/GrPathRendererChain.cpp +++ b/gpu/src/GrPathRendererChain.cpp @@ -43,7 +43,7 @@ GrPathRenderer* GrPathRendererChain::getPathRenderer(const GrDrawTarget* target, !target->getRenderTarget()->isMultisampled(); GrPathRenderer* nonAAPR = NULL; for (int i = 0; i < fChain.count(); ++i) { - if (fChain[i]->canDrawPath(path, fill)) { + if (fChain[i]->canDrawPath(target, path, fill)) { if (!preferAA || fChain[i]->supportsAA(target, path, fill)) { return fChain[i]; } else { diff --git a/gpu/src/GrTesselatedPathRenderer.cpp b/gpu/src/GrTesselatedPathRenderer.cpp index aca6ec2f4c..15e3cc0230 100644 --- a/gpu/src/GrTesselatedPathRenderer.cpp +++ b/gpu/src/GrTesselatedPathRenderer.cpp @@ -599,7 +599,8 @@ FINISHED: } } -bool GrTesselatedPathRenderer::canDrawPath(const SkPath& path, +bool GrTesselatedPathRenderer::canDrawPath(const GrDrawTarget* target, + const SkPath& path, GrPathFill fill) const { return kHairLine_PathFill != fill; } diff --git a/gpu/src/GrTesselatedPathRenderer.h b/gpu/src/GrTesselatedPathRenderer.h index c815f50857..d5bf15777b 100644 --- a/gpu/src/GrTesselatedPathRenderer.h +++ b/gpu/src/GrTesselatedPathRenderer.h @@ -17,7 +17,8 @@ public: GrTesselatedPathRenderer(); virtual void drawPath(GrDrawTarget::StageBitfield stages); - virtual bool canDrawPath(const GrPath& path, + virtual bool canDrawPath(const GrDrawTarget* target, + const GrPath& path, GrPathFill fill) const; virtual bool requiresStencilPass(const GrDrawTarget* target, diff --git a/gyp/SampleApp.gyp b/gyp/SampleApp.gyp index 92d6e74707..a3f4017067 100644 --- a/gyp/SampleApp.gyp +++ b/gyp/SampleApp.gyp @@ -34,6 +34,7 @@ '../samplecode/ClockFaceView.cpp', '../samplecode/OverView.cpp', '../samplecode/Sample2PtRadial.cpp', + '../samplecode/SampleAARects.cpp', '../samplecode/SampleAll.cpp', '../samplecode/SampleAnimator.cpp', '../samplecode/SampleApp.cpp', @@ -68,6 +69,7 @@ '../samplecode/SampleFuzz.cpp', '../samplecode/SampleGM.cpp', '../samplecode/SampleGradients.cpp', + '../samplecode/SampleHairCurves.cpp', '../samplecode/SampleHairline.cpp', '../samplecode/SampleImage.cpp', '../samplecode/SampleImageDir.cpp', @@ -90,7 +92,6 @@ '../samplecode/SamplePicture.cpp', '../samplecode/SamplePoints.cpp', '../samplecode/SamplePolyToPoly.cpp', - '../samplecode/SampleAARects.cpp', '../samplecode/SampleRegion.cpp', '../samplecode/SampleRepeatTile.cpp', '../samplecode/SampleShaders.cpp', diff --git a/gyp/gpu.gyp b/gyp/gpu.gyp index b7fa1c85e1..f77add2dad 100644 --- a/gyp/gpu.gyp +++ b/gyp/gpu.gyp @@ -126,7 +126,9 @@ '../gpu/include/GrTypes.h', '../gpu/include/GrUserConfig.h', - '../gpu/src/GrAddPathRenderers_none.cpp', + '../gpu/src/GrAAHairlinePathRenderer.cpp', + '../gpu/src/GrAAHairlinePathRenderer.h', + '../gpu/src/GrAddPathRenderers_aahairline.cpp', '../gpu/src/GrAllocPool.cpp', '../gpu/src/GrAtlas.cpp', '../gpu/src/GrBinHashKey.h', diff --git a/samplecode/SampleHairCurves.cpp b/samplecode/SampleHairCurves.cpp new file mode 100644 index 0000000000..152cbeb09c --- /dev/null +++ b/samplecode/SampleHairCurves.cpp @@ -0,0 +1,112 @@ + +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SampleCode.h" +#include "SkView.h" +#include "SkCanvas.h" +#include "SkPath.h" +#include "SkRandom.h" + +class HairCurvesView : public SampleView { +public: + HairCurvesView() { + } + +protected: + // overrides from SkEventSink + virtual bool onQuery(SkEvent* evt) { + if (SampleCode::TitleQ(*evt)) { + SampleCode::TitleR(evt, "HairCurves"); + return true; + } + return this->INHERITED::onQuery(evt); + } + + + virtual void onDrawContent(SkCanvas* canvas) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(-1); + canvas->save(); + canvas->scale(1000 * SK_Scalar1, 1000 * SK_Scalar1); + SkRandom rand; + SkPath curves; + SkPath hulls; + SkPath ctrlPts; + for (int i = 0; i < 100; ++i) { + SkScalar pts[] = { + rand.nextUScalar1(), rand.nextUScalar1(), + rand.nextUScalar1(), rand.nextUScalar1(), + rand.nextUScalar1(), rand.nextUScalar1(), + rand.nextUScalar1(), rand.nextUScalar1() + }; + curves.moveTo(pts[0], pts[1]); + curves.cubicTo(pts[2], pts[3], + pts[4], pts[5], + pts[6], pts[7]); + + hulls.moveTo(pts[0], pts[1]); + hulls.lineTo(pts[2], pts[3]); + hulls.lineTo(pts[4], pts[5]); + hulls.lineTo(pts[6], pts[7]); + + ctrlPts.addCircle(pts[0], pts[1], SK_Scalar1 / 200); + ctrlPts.addCircle(pts[2], pts[3], SK_Scalar1 / 200); + ctrlPts.addCircle(pts[4], pts[5], SK_Scalar1 / 200); + ctrlPts.addCircle(pts[6], pts[7], SK_Scalar1 / 200); + } + for (int i = 0; i < 100; ++i) { + SkScalar pts[] = { + rand.nextUScalar1(), rand.nextUScalar1(), + rand.nextUScalar1(), rand.nextUScalar1(), + rand.nextUScalar1(), rand.nextUScalar1(), + }; + curves.moveTo(pts[0], pts[1]); + curves.quadTo(pts[2], pts[3], + pts[4], pts[5]); + + hulls.moveTo(pts[0], pts[1]); + hulls.lineTo(pts[2], pts[3]); + hulls.lineTo(pts[4], pts[5]); + + ctrlPts.addCircle(pts[0], pts[1], SK_Scalar1 / 200); + ctrlPts.addCircle(pts[2], pts[3], SK_Scalar1 / 200); + ctrlPts.addCircle(pts[4], pts[5], SK_Scalar1 / 200); + } + for (int i = 0; i < 100; ++i) { + SkScalar pts[] = { + rand.nextUScalar1(), rand.nextUScalar1(), + rand.nextUScalar1(), rand.nextUScalar1(), + }; + curves.moveTo(pts[0], pts[1]); + curves.lineTo(pts[2], pts[3]); + + ctrlPts.addCircle(pts[0], pts[1], SK_Scalar1 / 200); + ctrlPts.addCircle(pts[2], pts[3], SK_Scalar1 / 200); + } + + paint.setColor(SK_ColorBLACK); + canvas->drawPath(curves, paint); + paint.setColor(SK_ColorRED); + //canvas->drawPath(hulls, paint); + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(SK_ColorBLUE); + //canvas->drawPath(ctrlPts, paint); + + canvas->restore(); + } + +private: + typedef SampleView INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +static SkView* MyFactory() { return new HairCurvesView; } +static SkViewRegister reg(MyFactory); + -- cgit v1.2.3