diff options
Diffstat (limited to 'src/gpu/GrAAHairLinePathRenderer.cpp')
-rw-r--r-- | src/gpu/GrAAHairLinePathRenderer.cpp | 730 |
1 files changed, 730 insertions, 0 deletions
diff --git a/src/gpu/GrAAHairLinePathRenderer.cpp b/src/gpu/GrAAHairLinePathRenderer.cpp new file mode 100644 index 0000000..29db9aa --- /dev/null +++ b/src/gpu/GrAAHairLinePathRenderer.cpp @@ -0,0 +1,730 @@ + +/* + * 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" + +#include "GrContext.h" +#include "GrDrawState.h" +#include "GrGpu.h" +#include "GrIndexBuffer.h" +#include "GrPathUtils.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) { + const GrIndexBuffer* lIdxBuffer = context->getQuadIndexBuffer(); + if (NULL == lIdxBuffer) { + return NULL; + } + GrGpu* gpu = context->getGpu(); + GrIndexBuffer* qIdxBuf = gpu->createIndexBuffer(kQuadIdxSBufize, false); + SkAutoTUnref<GrIndexBuffer> qIdxBuffer(qIdxBuf); + if (NULL == qIdxBuf || + !push_quad_index_data(qIdxBuf)) { + return NULL; + } + return new GrAAHairLinePathRenderer(context, + lIdxBuffer, + qIdxBuf); +} + +GrAAHairLinePathRenderer::GrAAHairLinePathRenderer( + const GrContext* context, + const GrIndexBuffer* linesIndexBuffer, + const GrIndexBuffer* quadsIndexBuffer) { + fLinesIndexBuffer = linesIndexBuffer; + linesIndexBuffer->ref(); + fQuadsIndexBuffer = quadsIndexBuffer; + quadsIndexBuffer->ref(); + this->resetGeom(); +} + +GrAAHairLinePathRenderer::~GrAAHairLinePathRenderer() { + fLinesIndexBuffer->unref(); + fQuadsIndexBuffer->unref(); +} + +bool GrAAHairLinePathRenderer::canDrawPath(const GrDrawTarget::Caps& targetCaps, + const SkPath& path, + GrPathFill fill, + bool antiAlias) const { + static const uint32_t gReqDerivMask = SkPath::kCubic_SegmentMask | + SkPath::kQuad_SegmentMask; + return (kHairLine_PathFill == fill && + antiAlias && + (targetCaps.fShaderDerivativeSupport || + !(gReqDerivMask & path.getSegmentMasks()))); +} + +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 SkTArray<SkPoint, true> PtArray; +#define PREALLOC_PTARRAY(N) SkSTArray<(N),SkPoint, true> +typedef SkTArray<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]; + ab -= p[0]; + SkVector dc = p[2]; + dc -= p[3]; + + static const SkScalar gLengthScale = 3 * SK_Scalar1 / 2; + // 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); + + SkVector c0 = p[0]; + c0 += ab; + SkVector c1 = p[3]; + c1 += dc; + + SkScalar dSqd = c0.distanceToSqd(c1); + if (sublevel > kMaxSubdivs || dSqd <= distanceSqdTol) { + SkPoint cAvg = c0; + cAvg += c1; + cAvg.scale(SK_ScalarHalf); + + SkPoint* pts = quads->push_back_n(3); + pts[0] = p[0]; + pts[1] = cAvg; + pts[2] = p[3]; + + return; + } else { + SkPoint choppedPts[7]; + SkChopCubicAtHalf(p, choppedPts); + 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], + 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, tolScale, 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 + const int* iptr = (const int*)&x; + return (((*iptr) & 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; + static const SkScalar gDegenerateToLineTolSqd = + SkScalarMul(gDegenerateToLineTol, gDegenerateToLineTol); + + if (p[0].distanceToSqd(p[1]) < gDegenerateToLineTolSqd || + p[1].distanceToSqd(p[2]) < gDegenerateToLineTolSqd) { + return -1; + } + + GrScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]); + if (dsqd < gDegenerateToLineTolSqd) { + return -1; + } + + if (p[2].distanceToLineBetweenSqd(p[1], p[0]) < gDegenerateToLineTolSqd) { + 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,gSubdivTol*gSubdivTol)); + static const SkScalar conv = SkScalarInvert(SkScalarLog(2)); + log = SkScalarMul(log, conv); + return GrMin(GrMax(0, SkScalarCeilToInt(log)),kMaxSub); +#endif + } +} + +/** + * 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: + 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)) { + SkPoint* pts = lines->push_back_n(2); + pts[0] = devPts[0]; + pts[1] = devPts[1]; + } + break; + 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)) { + int subdiv = num_quad_subdivs(devPts); + GrAssert(subdiv >= -1); + if (-1 == subdiv) { + SkPoint* pts = lines->push_back_n(4); + pts[0] = devPts[0]; + pts[1] = devPts[1]; + pts[2] = devPts[1]; + pts[3] = devPts[2]; + } else { + // when in perspective keep quads in src space + SkPoint* qPts = persp ? pts : devPts; + SkPoint* pts = quads->push_back_n(3); + pts[0] = qPts[0]; + pts[1] = qPts[1]; + pts[2] = qPts[2]; + quadSubdivCnts->push_back() = subdiv; + totalQuadCount += 1 << subdiv; + } + } + 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)) { + PREALLOC_PTARRAY(32) 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) { + 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(qInDevSpace); + GrAssert(subdiv >= -1); + if (-1 == subdiv) { + SkPoint* pts = lines->push_back_n(4); + // lines should always be in device coords + pts[0] = qInDevSpace[0]; + pts[1] = qInDevSpace[1]; + pts[2] = qInDevSpace[1]; + pts[3] = qInDevSpace[2]; + } else { + SkPoint* pts = quads->push_back_n(3); + // q is already in src space when there is no + // perspective and dev coords otherwise. + pts[0] = q[0 + i]; + pts[1] = q[1 + i]; + pts[2] = 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], const GrMatrix* toDevice, + const GrMatrix* toSrc, Vertex verts[kVertsPerQuad]) { + GrAssert(!toDevice == !toSrc); + // original quad is specified by tri a,b,c + 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, + 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); + + 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; + 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); + + 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, toDevice, toSrc, vert); + add_quads(newP + 2, subdiv-1, toDevice, toSrc, vert); + } else { + 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]; + + 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; + } + 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); + (*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(GrDrawState::StageMask stageMask) { + const GrDrawState& drawState = fTarget->getDrawState(); + int rtHeight = drawState.getRenderTarget()->height(); + + GrIRect clip; + if (fTarget->getClip().hasConservativeBounds()) { + GrRect clipRect = fTarget->getClip().getConservativeBounds(); + clipRect.roundOut(&clip); + } else { + 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 (stageMask == fPreviousStages && + fPreviousViewMatrix == drawState.getViewMatrix() && + fPreviousTranslate == fTranslate && + rtHeight == fPreviousRTHeight && + fClipRect == clip) { + return true; + } + + GrVertexLayout layout = GrDrawTarget::kEdge_VertexLayoutBit; + for (int s = 0; s < GrDrawState::kNumStages; ++s) { + if ((1 << s) & stageMask) { + layout |= GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(s); + } + } + + GrMatrix viewM = drawState.getViewMatrix(); + + PREALLOC_PTARRAY(128) lines; + PREALLOC_PTARRAY(128) quads; + IntArray qSubdivs; + fQuadCnt = generate_lines_and_quads(*fPath, viewM, fTranslate, 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; + } + 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, 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], toDevice, toSrc, &verts); + } + + fPreviousStages = stageMask; + fPreviousViewMatrix = drawState.getViewMatrix(); + fPreviousRTHeight = rtHeight; + fClipRect = clip; + fPreviousTranslate = fTranslate; + return true; +} + +void GrAAHairLinePathRenderer::drawPath(GrDrawState::StageMask stageMask) { + + if (!this->createGeom(stageMask)) { + return; + } + + GrDrawState* drawState = fTarget->drawState(); + + GrDrawTarget::AutoStateRestore asr; + if (!drawState->getViewMatrix().hasPerspective()) { + asr.set(fTarget); + GrMatrix ivm; + if (drawState->getViewInverse(&ivm)) { + drawState->preConcatSamplerMatrices(stageMask, ivm); + } + drawState->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); + drawState->setVertexEdgeType(GrDrawState::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); + drawState->setVertexEdgeType(GrDrawState::kHairQuad_EdgeType); + fTarget->drawIndexed(kTriangles_PrimitiveType, + 4*fLineSegmentCnt + kVertsPerQuad*quads, // startV + 0, // startI + kVertsPerQuad*n, // vCount + kIdxsPerQuad*n); // iCount + quads += n; + } + +} + |