blob: 13aa00158dcd74a7b28e92cd7653be41864457a8 [file] [log] [blame]
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001#include "GrAAHairLinePathRenderer.h"
2
3#include "GrContext.h"
4#include "GrGpu.h"
5#include "GrIndexBuffer.h"
6#include "SkGeometry.h"
7#include "SkTemplates.h"
8
9namespace {
10// quadratics are rendered as 5-sided polys in order to bound the
11// AA stroke around the center-curve. See comments in push_quad_index_buffer and
12// bloat_quad.
13static const int kVertsPerQuad = 5;
14static const int kIdxsPerQuad = 9;
15
16static const int kVertsPerLineSeg = 4;
17static const int kIdxsPerLineSeg = 6;
18
19static const int kNumQuadsInIdxBuffer = 256;
20static const size_t kQuadIdxSBufize = kIdxsPerQuad *
21 sizeof(uint16_t) *
22 kNumQuadsInIdxBuffer;
23
24bool push_quad_index_data(GrIndexBuffer* qIdxBuffer) {
25 uint16_t* data = (uint16_t*) qIdxBuffer->lock();
26 bool tempData = NULL == data;
27 if (tempData) {
28 data = new uint16_t[kNumQuadsInIdxBuffer * kIdxsPerQuad];
29 }
30 for (int i = 0; i < kNumQuadsInIdxBuffer; ++i) {
31
32 // Each quadratic is rendered as a five sided polygon. This poly bounds
33 // the quadratic's bounding triangle but has been expanded so that the
34 // 1-pixel wide area around the curve is inside the poly.
35 // If a,b,c are the original control points then the poly a0,b0,c0,c1,a1
36 // that is rendered would look like this:
37 // b0
38 // b
39 //
40 // a0 c0
41 // a c
42 // a1 c1
43 // Each is drawn as three triagnles specified by these 9 indices:
44 int baseIdx = i * kIdxsPerQuad;
45 uint16_t baseVert = (uint16_t)(i * kVertsPerQuad);
46 data[0 + baseIdx] = baseVert + 0; // a0
47 data[1 + baseIdx] = baseVert + 1; // a1
48 data[2 + baseIdx] = baseVert + 2; // b0
49 data[3 + baseIdx] = baseVert + 2; // b0
50 data[4 + baseIdx] = baseVert + 4; // c1
51 data[5 + baseIdx] = baseVert + 3; // c0
52 data[6 + baseIdx] = baseVert + 1; // a1
53 data[7 + baseIdx] = baseVert + 4; // c1
54 data[8 + baseIdx] = baseVert + 2; // b0
55 }
56 if (tempData) {
57 bool ret = qIdxBuffer->updateData(data, kQuadIdxSBufize);
58 delete[] data;
59 return ret;
60 } else {
61 qIdxBuffer->unlock();
62 return true;
63 }
64}
65}
66
67GrPathRenderer* GrAAHairLinePathRenderer::Create(GrContext* context) {
68 if (CanBeUsed(context)) {
69 const GrIndexBuffer* lIdxBuffer = context->getQuadIndexBuffer();
70 if (NULL == lIdxBuffer) {
71 return NULL;
72 }
73 GrGpu* gpu = context->getGpu();
74 GrIndexBuffer* qIdxBuf = gpu->createIndexBuffer(kQuadIdxSBufize, false);
75 SkAutoTUnref<GrIndexBuffer> qIdxBuffer(qIdxBuf); // cons will take a ref
76 if (NULL == qIdxBuf ||
77 !push_quad_index_data(qIdxBuffer.get())) {
78 return NULL;
79 }
80 return new GrAAHairLinePathRenderer(context,
81 lIdxBuffer,
82 qIdxBuf);
83 } else {
84 return NULL;
85 }
86}
87
88bool GrAAHairLinePathRenderer::CanBeUsed(const GrContext* context) {
89 return context->getGpu()->supportsShaderDerivatives();
90
91}
92
93GrAAHairLinePathRenderer::GrAAHairLinePathRenderer(
94 const GrContext* context,
95 const GrIndexBuffer* linesIndexBuffer,
96 const GrIndexBuffer* quadsIndexBuffer) {
97 GrAssert(CanBeUsed(context));
98 fLinesIndexBuffer = linesIndexBuffer;
99 linesIndexBuffer->ref();
100 fQuadsIndexBuffer = quadsIndexBuffer;
101 quadsIndexBuffer->ref();
102 this->resetGeom();
103}
104
105GrAAHairLinePathRenderer::~GrAAHairLinePathRenderer() {
106 fLinesIndexBuffer->unref();
107 fQuadsIndexBuffer->unref();
108}
109
110bool GrAAHairLinePathRenderer::supportsAA(GrDrawTarget* target,
111 const SkPath& path,
112 GrPathFill fill) {
113 return kHairLine_PathFill == fill;
114}
115
116bool GrAAHairLinePathRenderer::canDrawPath(const GrDrawTarget* target,
117 const SkPath& path,
118 GrPathFill fill) const {
119 // TODO: support perspective
120 return kHairLine_PathFill == fill &&
121 !target->getViewMatrix().hasPerspective();
122}
123
124void GrAAHairLinePathRenderer::pathWillClear() {
125 this->resetGeom();
126}
127
128void GrAAHairLinePathRenderer::resetGeom() {
129 fPreviousStages = ~0;
130 fPreviousRTHeight = ~0;
131 fPreviousViewMatrix = GrMatrix::InvalidMatrix();
132 fLineSegmentCnt = 0;
133 fQuadCnt = 0;
134 if ((fQuadCnt || fLineSegmentCnt) && NULL != fTarget) {
135 fTarget->resetVertexSource();
136 }
137}
138
139namespace {
140
141typedef GrTArray<SkPoint, true> PtArray;
142typedef GrTArray<int, true> IntArray;
143
144/**
145 * We convert cubics to quadratics (for now).
146 */
147void convert_noninflect_cubic_to_quads(const SkPoint p[4],
148 PtArray* quads,
149 int sublevel = 0) {
150 SkVector ab = p[1];
151 ab -= p[0];
152 SkVector dc = p[2];
153 dc -= p[3];
154
155 static const SkScalar gLengthScale = 3 * SK_Scalar1 / 2;
156 static const SkScalar gDistanceSqdTol = 2 * SK_Scalar1;
157 static const int kMaxSubdivs = 30;
158
159 ab.scale(gLengthScale);
160 dc.scale(gLengthScale);
161
162 SkVector c0 = p[0];
163 c0 += ab;
164 SkVector c1 = p[3];
165 c1 += dc;
166
167 SkScalar dSqd = c0.distanceToSqd(c1);
168 if (sublevel > kMaxSubdivs || dSqd <= gDistanceSqdTol) {
169 SkPoint cAvg = c0;
170 cAvg += c1;
171 cAvg.scale(SK_ScalarHalf);
172
173 int idx = quads->count();
174 quads->push_back_n(3);
175 (*quads)[idx+0] = p[0];
176 (*quads)[idx+1] = cAvg;
177 (*quads)[idx+2] = p[3];
178
179 return;
180 } else {
181 SkPoint choppedPts[7];
182 SkChopCubicAtHalf(p, choppedPts);
183 convert_noninflect_cubic_to_quads(choppedPts + 0, quads, sublevel + 1);
184 convert_noninflect_cubic_to_quads(choppedPts + 3, quads, sublevel + 1);
185 }
186}
187
188void convert_cubic_to_quads(const SkPoint p[4], PtArray* quads) {
189 SkPoint chopped[13];
190 int count = SkChopCubicAtInflections(p, chopped);
191
192 for (int i = 0; i < count; ++i) {
193 SkPoint* cubic = chopped + 3*i;
194 convert_noninflect_cubic_to_quads(cubic, quads);
195 }
196}
197
198// Takes 178th time of logf on Z600 / VC2010
199int get_float_exp(float x) {
200 GR_STATIC_ASSERT(sizeof(int) == sizeof(float));
201#if GR_DEBUG
202 static bool tested;
203 if (!tested) {
204 tested = true;
205 GrAssert(get_float_exp(0.25f) == -2);
206 GrAssert(get_float_exp(0.3f) == -2);
207 GrAssert(get_float_exp(0.5f) == -1);
208 GrAssert(get_float_exp(1.f) == 0);
209 GrAssert(get_float_exp(2.f) == 1);
210 GrAssert(get_float_exp(2.5f) == 1);
211 GrAssert(get_float_exp(8.f) == 3);
212 GrAssert(get_float_exp(100.f) == 6);
213 GrAssert(get_float_exp(1000.f) == 9);
214 GrAssert(get_float_exp(1024.f) == 10);
215 GrAssert(get_float_exp(3000000.f) == 21);
216 }
217#endif
218 return (((*(int*)&x) & 0x7f800000) >> 23) - 127;
219}
220
221// we subdivide the quads to avoid huge overfill
222// if it returns -1 then should be drawn as lines
223int num_quad_subdivs(const SkPoint p[3]) {
224 static const SkScalar gDegenerateToLineTol = SK_Scalar1;
bsalomon@google.com46a2a1e2011-09-06 22:10:52 +0000225 static const SkScalar gDegenerateToLineTolSqd =
226 SkScalarMul(gDegenerateToLineTol, gDegenerateToLineTol);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000227
bsalomon@google.com46a2a1e2011-09-06 22:10:52 +0000228 if (p[0].distanceToSqd(p[1]) < gDegenerateToLineTolSqd ||
229 p[1].distanceToSqd(p[2]) < gDegenerateToLineTolSqd) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000230 return -1;
231 }
bsalomon@google.com46a2a1e2011-09-06 22:10:52 +0000232
233 GrScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]);
234 if (dsqd < gDegenerateToLineTolSqd) {
235 return -1;
236 }
237
238 if (p[2].distanceToLineBetweenSqd(p[1], p[0]) < gDegenerateToLineTolSqd) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000239 return -1;
240 }
241
242 static const int kMaxSub = 4;
243 // tolerance of triangle height in pixels
244 // tuned on windows Quadro FX 380 / Z600
245 // trade off of fill vs cpu time on verts
246 // maybe different when do this using gpu (geo or tess shaders)
247 static const SkScalar gSubdivTol = 175 * SK_Scalar1;
248
249 if (dsqd <= gSubdivTol*gSubdivTol) {
250 return 0;
251 } else {
252 // subdividing the quad reduces d by 4. so we want x = log4(d/tol)
253 // = log4(d*d/tol*tol)/2
254 // = log2(d*d/tol*tol)
255
256#ifdef SK_SCALAR_IS_FLOAT
257 // +1 since we're ignoring the mantissa contribution.
258 int log = get_float_exp(dsqd/(gSubdivTol*gSubdivTol)) + 1;
259 log = GrMin(GrMax(0, log), kMaxSub);
260 return log;
261#else
bsalomon@google.comfcb0dbc2011-08-30 18:35:18 +0000262 SkScalar log = SkScalarLog(SkScalarDiv(dsqd,gSubdivTol*gSubdivTol));
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000263 static const SkScalar conv = SkScalarInvert(SkScalarLog(2));
264 log = SkScalarMul(log, conv);
265 return GrMin(GrMax(0, SkScalarCeilToInt(log)),kMaxSub);
266#endif
267 }
268}
269
270int get_lines_and_quads(const SkPath& path, const SkMatrix& m, GrIRect clip,
271 PtArray* lines, PtArray* quads,
272 IntArray* quadSubdivCnts) {
273 SkPath::Iter iter(path, false);
274
275 int totalQuadCount = 0;
276 GrRect bounds;
277 GrIRect ibounds;
278 for (;;) {
279 GrPoint pts[4];
280 GrPathCmd cmd = (GrPathCmd)iter.next(pts);
281 switch (cmd) {
282 case kMove_PathCmd:
283 break;
284 case kLine_PathCmd:
285 m.mapPoints(pts,2);
286 bounds.setBounds(pts, 2);
287 bounds.outset(SK_Scalar1, SK_Scalar1);
288 bounds.roundOut(&ibounds);
289 if (SkIRect::Intersects(clip, ibounds)) {
290 lines->push_back() = pts[0];
291 lines->push_back() = pts[1];
292 }
293 break;
294 case kQuadratic_PathCmd: {
295 bounds.setBounds(pts, 3);
296 bounds.outset(SK_Scalar1, SK_Scalar1);
297 bounds.roundOut(&ibounds);
298 if (SkIRect::Intersects(clip, ibounds)) {
299 m.mapPoints(pts, 3);
300 int subdiv = num_quad_subdivs(pts);
301 GrAssert(subdiv >= -1);
302 if (-1 == subdiv) {
303 lines->push_back() = pts[0];
304 lines->push_back() = pts[1];
305 lines->push_back() = pts[1];
306 lines->push_back() = pts[2];
307 } else {
308 quads->push_back() = pts[0];
309 quads->push_back() = pts[1];
310 quads->push_back() = pts[2];
311 quadSubdivCnts->push_back() = subdiv;
312 totalQuadCount += 1 << subdiv;
313 }
314 }
315 } break;
316 case kCubic_PathCmd: {
317 bounds.setBounds(pts, 4);
318 bounds.outset(SK_Scalar1, SK_Scalar1);
319 bounds.roundOut(&ibounds);
320 if (SkIRect::Intersects(clip, ibounds)) {
321 m.mapPoints(pts, 4);
322 SkPoint stackStorage[32];
323 PtArray q((void*)stackStorage, 32);
324 convert_cubic_to_quads(pts, &q);
325 for (int i = 0; i < q.count(); i += 3) {
326 bounds.setBounds(&q[i], 3);
327 bounds.outset(SK_Scalar1, SK_Scalar1);
328 bounds.roundOut(&ibounds);
329 if (SkIRect::Intersects(clip, ibounds)) {
330 int subdiv = num_quad_subdivs(&q[i]);
331 GrAssert(subdiv >= -1);
332 if (-1 == subdiv) {
333 lines->push_back() = q[0 + i];
334 lines->push_back() = q[1 + i];
335 lines->push_back() = q[1 + i];
336 lines->push_back() = q[2 + i];
337 } else {
338 quads->push_back() = q[0 + i];
339 quads->push_back() = q[1 + i];
340 quads->push_back() = q[2 + i];
341 quadSubdivCnts->push_back() = subdiv;
342 totalQuadCount += 1 << subdiv;
343 }
344 }
345 }
346 }
347 } break;
348 case kClose_PathCmd:
349 break;
350 case kEnd_PathCmd:
351 return totalQuadCount;
352 }
353 }
354}
355
356struct Vertex {
357 GrPoint fPos;
358 union {
359 struct {
360 GrScalar fA;
361 GrScalar fB;
362 GrScalar fC;
363 } fLine;
364 GrVec fQuadCoord;
365 struct {
366 GrScalar fBogus[4];
367 };
368 };
369};
370GR_STATIC_ASSERT(sizeof(Vertex) == 3 * sizeof(GrPoint));
371
372void intersect_lines(const SkPoint& ptA, const SkVector& normA,
373 const SkPoint& ptB, const SkVector& normB,
374 SkPoint* result) {
375
376 SkScalar lineAW = -normA.dot(ptA);
377 SkScalar lineBW = -normB.dot(ptB);
378
379 SkScalar wInv = SkScalarMul(normA.fX, normB.fY) -
380 SkScalarMul(normA.fY, normB.fX);
381 wInv = SkScalarInvert(wInv);
382
383 result->fX = SkScalarMul(normA.fY, lineBW) - SkScalarMul(lineAW, normB.fY);
384 result->fX = SkScalarMul(result->fX, wInv);
385
386 result->fY = SkScalarMul(lineAW, normB.fX) - SkScalarMul(normA.fX, lineBW);
387 result->fY = SkScalarMul(result->fY, wInv);
388}
389
390void bloat_quad(const SkPoint qpts[3], Vertex verts[kVertsPerQuad]) {
391 // original quad is specified by tri a,b,c
392 const SkPoint& a = qpts[0];
393 const SkPoint& b = qpts[1];
394 const SkPoint& c = qpts[2];
395 // make a new poly where we replace a and c by a 1-pixel wide edges orthog
396 // to edges ab and bc:
397 //
398 // before | after
399 // | b0
400 // b |
401 // |
402 // | a0 c0
403 // a c | a1 c1
404 //
405 // edges a0->b0 and b0->c0 are parallel to original edges a->b and b->c,
406 // respectively.
407 Vertex& a0 = verts[0];
408 Vertex& a1 = verts[1];
409 Vertex& b0 = verts[2];
410 Vertex& c0 = verts[3];
411 Vertex& c1 = verts[4];
412
413 // compute a matrix that goes from device coords to U,V quad params
414 SkMatrix DevToUV;
415 DevToUV.setAll(a.fX, b.fX, c.fX,
416 a.fY, b.fY, c.fY,
417 SK_Scalar1, SK_Scalar1, SK_Scalar1);
418 DevToUV.invert(&DevToUV);
419 // can't make this static, no cons :(
420 SkMatrix UVpts;
421 UVpts.setAll(0, SK_ScalarHalf, SK_Scalar1,
422 0, 0, SK_Scalar1,
423 SK_Scalar1, SK_Scalar1, SK_Scalar1);
424 DevToUV.postConcat(UVpts);
425
426 // We really want to avoid perspective matrix muls.
427 // These may wind up really close to zero
428 DevToUV.setPerspX(0);
429 DevToUV.setPerspY(0);
430
431 SkVector ab = b;
432 ab -= a;
433 SkVector ac = c;
434 ac -= a;
435 SkVector cb = b;
436 cb -= c;
437
438 // We should have already handled degenerates
439 GrAssert(ab.length() > 0 && cb.length() > 0);
440
441 ab.normalize();
442 SkVector abN;
443 abN.setOrthog(ab, SkVector::kLeft_Side);
444 if (abN.dot(ac) > 0) {
445 abN.negate();
446 }
447
448 cb.normalize();
449 SkVector cbN;
450 cbN.setOrthog(cb, SkVector::kLeft_Side);
451 if (cbN.dot(ac) < 0) {
452 cbN.negate();
453 }
454
455 a0.fPos = a;
456 a0.fPos += abN;
457 a1.fPos = a;
458 a1.fPos -= abN;
459
460 c0.fPos = c;
461 c0.fPos += cbN;
462 c1.fPos = c;
463 c1.fPos -= cbN;
464
465 intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos);
466
467 DevToUV.mapPointsWithStride(&verts[0].fQuadCoord,
468 &verts[0].fPos, sizeof(Vertex), kVertsPerQuad);
469}
470
471void add_quads(const SkPoint p[3],
472 int subdiv,
473 Vertex** vert) {
474 GrAssert(subdiv >= 0);
475 if (subdiv) {
476 SkPoint newP[5];
477 SkChopQuadAtHalf(p, newP);
478 add_quads(newP + 0, subdiv-1, vert);
479 add_quads(newP + 2, subdiv-1, vert);
480 } else {
481 bloat_quad(p, *vert);
482 *vert += kVertsPerQuad;
483 }
484}
485
486void add_line(const SkPoint p[2],
487 int rtHeight,
488 Vertex** vert) {
489 const SkPoint& a = p[0];
490 const SkPoint& b = p[1];
491
492 SkVector orthVec = b;
493 orthVec -= a;
494
495 if (orthVec.setLength(SK_Scalar1)) {
496 orthVec.setOrthog(orthVec);
497
498 // the values we pass down to the frag shader
499 // have to be in y-points-up space;
500 SkVector normal;
501 normal.fX = orthVec.fX;
502 normal.fY = -orthVec.fY;
503 SkPoint aYDown;
504 aYDown.fX = a.fX;
505 aYDown.fY = rtHeight - a.fY;
506
507 SkScalar lineC = -(aYDown.dot(normal));
508 for (int i = 0; i < kVertsPerLineSeg; ++i) {
509 (*vert)[i].fPos = (i < 2) ? a : b;
510 if (0 == i || 3 == i) {
511 (*vert)[i].fPos -= orthVec;
512 } else {
513 (*vert)[i].fPos += orthVec;
514 }
515 (*vert)[i].fLine.fA = normal.fX;
516 (*vert)[i].fLine.fB = normal.fY;
517 (*vert)[i].fLine.fC = lineC;
518 }
519 } else {
520 // just make it degenerate and likely offscreen
521 (*vert)[0].fPos.set(SK_ScalarMax, SK_ScalarMax);
522 (*vert)[1].fPos.set(SK_ScalarMax, SK_ScalarMax);
523 (*vert)[2].fPos.set(SK_ScalarMax, SK_ScalarMax);
524 (*vert)[3].fPos.set(SK_ScalarMax, SK_ScalarMax);
525 }
526
527 *vert += kVertsPerLineSeg;
528}
529
530}
531
532bool GrAAHairLinePathRenderer::createGeom(GrDrawTarget::StageBitfield stages) {
533
534 int rtHeight = fTarget->getRenderTarget()->height();
535
536 GrIRect clip;
537 if (fTarget->getClip().hasConservativeBounds()) {
538 GrRect clipRect = fTarget->getClip().getConservativeBounds();
539 clipRect.roundOut(&clip);
540 } else {
541 clip.setLargest();
542 }
543
544 if (stages == fPreviousStages &&
545 fPreviousViewMatrix == fTarget->getViewMatrix() &&
546 rtHeight == fPreviousRTHeight &&
547 fClipRect == clip) {
548 return true;
549 }
550
551 GrVertexLayout layout = GrDrawTarget::kEdge_VertexLayoutBit;
552 for (int s = 0; s < GrDrawTarget::kNumStages; ++s) {
553 if ((1 << s) & stages) {
554 layout |= GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(s);
555 }
556 }
557
558 GrMatrix viewM = fTarget->getViewMatrix();
559 viewM.preTranslate(fTranslate.fX, fTranslate.fY);
560
561 GrAlignedSTStorage<128, GrPoint> lineStorage;
562 GrAlignedSTStorage<128, GrPoint> quadStorage;
563 PtArray lines(&lineStorage);
564 PtArray quads(&quadStorage);
565 IntArray qSubdivs;
566 fQuadCnt = get_lines_and_quads(*fPath, viewM, clip,
567 &lines, &quads, &qSubdivs);
568
569 fLineSegmentCnt = lines.count() / 2;
570 int vertCnt = kVertsPerLineSeg * fLineSegmentCnt + kVertsPerQuad * fQuadCnt;
571
572 GrAssert(sizeof(Vertex) == GrDrawTarget::VertexSize(layout));
573
574 Vertex* verts;
575 if (!fTarget->reserveVertexSpace(layout, vertCnt, (void**)&verts)) {
576 return false;
577 }
578
579 for (int i = 0; i < fLineSegmentCnt; ++i) {
580 add_line(&lines[2*i], rtHeight, &verts);
581 }
582 int unsubdivQuadCnt = quads.count() / 3;
583 for (int i = 0; i < unsubdivQuadCnt; ++i) {
584 GrAssert(qSubdivs[i] >= 0);
585 add_quads(&quads[3*i], qSubdivs[i], &verts);
586 }
587
588 fPreviousStages = stages;
589 fPreviousViewMatrix = fTarget->getViewMatrix();
590 fPreviousRTHeight = rtHeight;
591 fClipRect = clip;
592 return true;
593}
594
595void GrAAHairLinePathRenderer::drawPath(GrDrawTarget::StageBitfield stages) {
596 GrDrawTarget::AutoStateRestore asr(fTarget);
597
598 GrMatrix ivm;
599
600 if (!this->createGeom(stages)) {
601 return;
602 }
603
604 if (fTarget->getViewInverse(&ivm)) {
605 fTarget->preConcatSamplerMatrices(stages, ivm);
606 }
607
608 fTarget->setViewMatrix(GrMatrix::I());
609
610 // TODO: See whether rendering lines as degenerate quads improves perf
611 // when we have a mix
612 fTarget->setIndexSourceToBuffer(fLinesIndexBuffer);
613 int lines = 0;
614 int nBufLines = fLinesIndexBuffer->maxQuads();
615 while (lines < fLineSegmentCnt) {
616 int n = GrMin(fLineSegmentCnt-lines, nBufLines);
617 fTarget->setVertexEdgeType(GrDrawTarget::kHairLine_EdgeType);
618 fTarget->drawIndexed(kTriangles_PrimitiveType,
619 kVertsPerLineSeg*lines, // startV
620 0, // startI
621 kVertsPerLineSeg*n, // vCount
622 kIdxsPerLineSeg*n); // iCount
623 lines += n;
624 }
625
626 fTarget->setIndexSourceToBuffer(fQuadsIndexBuffer);
627 int quads = 0;
628 while (quads < fQuadCnt) {
629 int n = GrMin(fQuadCnt-quads, kNumQuadsInIdxBuffer);
630 fTarget->setVertexEdgeType(GrDrawTarget::kHairQuad_EdgeType);
631 fTarget->drawIndexed(kTriangles_PrimitiveType,
632 4*fLineSegmentCnt + kVertsPerQuad*quads, // startV
633 0, // startI
634 kVertsPerQuad*n, // vCount
635 kIdxsPerQuad*n); // iCount
636 quads += n;
637 }
638
639}
640