blob: 40722b8eb2cbcf9b1b5e5f91542777e2f22ead13 [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;
225
226 GrScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]);
227 if (dsqd < gDegenerateToLineTol*gDegenerateToLineTol) {
228 return -1;
229 }
230 if (p[2].distanceToLineBetweenSqd(p[1],p[0]) <
231 gDegenerateToLineTol*gDegenerateToLineTol) {
232 return -1;
233 }
234
235 static const int kMaxSub = 4;
236 // tolerance of triangle height in pixels
237 // tuned on windows Quadro FX 380 / Z600
238 // trade off of fill vs cpu time on verts
239 // maybe different when do this using gpu (geo or tess shaders)
240 static const SkScalar gSubdivTol = 175 * SK_Scalar1;
241
242 if (dsqd <= gSubdivTol*gSubdivTol) {
243 return 0;
244 } else {
245 // subdividing the quad reduces d by 4. so we want x = log4(d/tol)
246 // = log4(d*d/tol*tol)/2
247 // = log2(d*d/tol*tol)
248
249#ifdef SK_SCALAR_IS_FLOAT
250 // +1 since we're ignoring the mantissa contribution.
251 int log = get_float_exp(dsqd/(gSubdivTol*gSubdivTol)) + 1;
252 log = GrMin(GrMax(0, log), kMaxSub);
253 return log;
254#else
255 SkScalar log = SkScalarLog(SkScalarDiv(dsqd,kTol*kTol));
256 static const SkScalar conv = SkScalarInvert(SkScalarLog(2));
257 log = SkScalarMul(log, conv);
258 return GrMin(GrMax(0, SkScalarCeilToInt(log)),kMaxSub);
259#endif
260 }
261}
262
263int get_lines_and_quads(const SkPath& path, const SkMatrix& m, GrIRect clip,
264 PtArray* lines, PtArray* quads,
265 IntArray* quadSubdivCnts) {
266 SkPath::Iter iter(path, false);
267
268 int totalQuadCount = 0;
269 GrRect bounds;
270 GrIRect ibounds;
271 for (;;) {
272 GrPoint pts[4];
273 GrPathCmd cmd = (GrPathCmd)iter.next(pts);
274 switch (cmd) {
275 case kMove_PathCmd:
276 break;
277 case kLine_PathCmd:
278 m.mapPoints(pts,2);
279 bounds.setBounds(pts, 2);
280 bounds.outset(SK_Scalar1, SK_Scalar1);
281 bounds.roundOut(&ibounds);
282 if (SkIRect::Intersects(clip, ibounds)) {
283 lines->push_back() = pts[0];
284 lines->push_back() = pts[1];
285 }
286 break;
287 case kQuadratic_PathCmd: {
288 bounds.setBounds(pts, 3);
289 bounds.outset(SK_Scalar1, SK_Scalar1);
290 bounds.roundOut(&ibounds);
291 if (SkIRect::Intersects(clip, ibounds)) {
292 m.mapPoints(pts, 3);
293 int subdiv = num_quad_subdivs(pts);
294 GrAssert(subdiv >= -1);
295 if (-1 == subdiv) {
296 lines->push_back() = pts[0];
297 lines->push_back() = pts[1];
298 lines->push_back() = pts[1];
299 lines->push_back() = pts[2];
300 } else {
301 quads->push_back() = pts[0];
302 quads->push_back() = pts[1];
303 quads->push_back() = pts[2];
304 quadSubdivCnts->push_back() = subdiv;
305 totalQuadCount += 1 << subdiv;
306 }
307 }
308 } break;
309 case kCubic_PathCmd: {
310 bounds.setBounds(pts, 4);
311 bounds.outset(SK_Scalar1, SK_Scalar1);
312 bounds.roundOut(&ibounds);
313 if (SkIRect::Intersects(clip, ibounds)) {
314 m.mapPoints(pts, 4);
315 SkPoint stackStorage[32];
316 PtArray q((void*)stackStorage, 32);
317 convert_cubic_to_quads(pts, &q);
318 for (int i = 0; i < q.count(); i += 3) {
319 bounds.setBounds(&q[i], 3);
320 bounds.outset(SK_Scalar1, SK_Scalar1);
321 bounds.roundOut(&ibounds);
322 if (SkIRect::Intersects(clip, ibounds)) {
323 int subdiv = num_quad_subdivs(&q[i]);
324 GrAssert(subdiv >= -1);
325 if (-1 == subdiv) {
326 lines->push_back() = q[0 + i];
327 lines->push_back() = q[1 + i];
328 lines->push_back() = q[1 + i];
329 lines->push_back() = q[2 + i];
330 } else {
331 quads->push_back() = q[0 + i];
332 quads->push_back() = q[1 + i];
333 quads->push_back() = q[2 + i];
334 quadSubdivCnts->push_back() = subdiv;
335 totalQuadCount += 1 << subdiv;
336 }
337 }
338 }
339 }
340 } break;
341 case kClose_PathCmd:
342 break;
343 case kEnd_PathCmd:
344 return totalQuadCount;
345 }
346 }
347}
348
349struct Vertex {
350 GrPoint fPos;
351 union {
352 struct {
353 GrScalar fA;
354 GrScalar fB;
355 GrScalar fC;
356 } fLine;
357 GrVec fQuadCoord;
358 struct {
359 GrScalar fBogus[4];
360 };
361 };
362};
363GR_STATIC_ASSERT(sizeof(Vertex) == 3 * sizeof(GrPoint));
364
365void intersect_lines(const SkPoint& ptA, const SkVector& normA,
366 const SkPoint& ptB, const SkVector& normB,
367 SkPoint* result) {
368
369 SkScalar lineAW = -normA.dot(ptA);
370 SkScalar lineBW = -normB.dot(ptB);
371
372 SkScalar wInv = SkScalarMul(normA.fX, normB.fY) -
373 SkScalarMul(normA.fY, normB.fX);
374 wInv = SkScalarInvert(wInv);
375
376 result->fX = SkScalarMul(normA.fY, lineBW) - SkScalarMul(lineAW, normB.fY);
377 result->fX = SkScalarMul(result->fX, wInv);
378
379 result->fY = SkScalarMul(lineAW, normB.fX) - SkScalarMul(normA.fX, lineBW);
380 result->fY = SkScalarMul(result->fY, wInv);
381}
382
383void bloat_quad(const SkPoint qpts[3], Vertex verts[kVertsPerQuad]) {
384 // original quad is specified by tri a,b,c
385 const SkPoint& a = qpts[0];
386 const SkPoint& b = qpts[1];
387 const SkPoint& c = qpts[2];
388 // make a new poly where we replace a and c by a 1-pixel wide edges orthog
389 // to edges ab and bc:
390 //
391 // before | after
392 // | b0
393 // b |
394 // |
395 // | a0 c0
396 // a c | a1 c1
397 //
398 // edges a0->b0 and b0->c0 are parallel to original edges a->b and b->c,
399 // respectively.
400 Vertex& a0 = verts[0];
401 Vertex& a1 = verts[1];
402 Vertex& b0 = verts[2];
403 Vertex& c0 = verts[3];
404 Vertex& c1 = verts[4];
405
406 // compute a matrix that goes from device coords to U,V quad params
407 SkMatrix DevToUV;
408 DevToUV.setAll(a.fX, b.fX, c.fX,
409 a.fY, b.fY, c.fY,
410 SK_Scalar1, SK_Scalar1, SK_Scalar1);
411 DevToUV.invert(&DevToUV);
412 // can't make this static, no cons :(
413 SkMatrix UVpts;
414 UVpts.setAll(0, SK_ScalarHalf, SK_Scalar1,
415 0, 0, SK_Scalar1,
416 SK_Scalar1, SK_Scalar1, SK_Scalar1);
417 DevToUV.postConcat(UVpts);
418
419 // We really want to avoid perspective matrix muls.
420 // These may wind up really close to zero
421 DevToUV.setPerspX(0);
422 DevToUV.setPerspY(0);
423
424 SkVector ab = b;
425 ab -= a;
426 SkVector ac = c;
427 ac -= a;
428 SkVector cb = b;
429 cb -= c;
430
431 // We should have already handled degenerates
432 GrAssert(ab.length() > 0 && cb.length() > 0);
433
434 ab.normalize();
435 SkVector abN;
436 abN.setOrthog(ab, SkVector::kLeft_Side);
437 if (abN.dot(ac) > 0) {
438 abN.negate();
439 }
440
441 cb.normalize();
442 SkVector cbN;
443 cbN.setOrthog(cb, SkVector::kLeft_Side);
444 if (cbN.dot(ac) < 0) {
445 cbN.negate();
446 }
447
448 a0.fPos = a;
449 a0.fPos += abN;
450 a1.fPos = a;
451 a1.fPos -= abN;
452
453 c0.fPos = c;
454 c0.fPos += cbN;
455 c1.fPos = c;
456 c1.fPos -= cbN;
457
458 intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos);
459
460 DevToUV.mapPointsWithStride(&verts[0].fQuadCoord,
461 &verts[0].fPos, sizeof(Vertex), kVertsPerQuad);
462}
463
464void add_quads(const SkPoint p[3],
465 int subdiv,
466 Vertex** vert) {
467 GrAssert(subdiv >= 0);
468 if (subdiv) {
469 SkPoint newP[5];
470 SkChopQuadAtHalf(p, newP);
471 add_quads(newP + 0, subdiv-1, vert);
472 add_quads(newP + 2, subdiv-1, vert);
473 } else {
474 bloat_quad(p, *vert);
475 *vert += kVertsPerQuad;
476 }
477}
478
479void add_line(const SkPoint p[2],
480 int rtHeight,
481 Vertex** vert) {
482 const SkPoint& a = p[0];
483 const SkPoint& b = p[1];
484
485 SkVector orthVec = b;
486 orthVec -= a;
487
488 if (orthVec.setLength(SK_Scalar1)) {
489 orthVec.setOrthog(orthVec);
490
491 // the values we pass down to the frag shader
492 // have to be in y-points-up space;
493 SkVector normal;
494 normal.fX = orthVec.fX;
495 normal.fY = -orthVec.fY;
496 SkPoint aYDown;
497 aYDown.fX = a.fX;
498 aYDown.fY = rtHeight - a.fY;
499
500 SkScalar lineC = -(aYDown.dot(normal));
501 for (int i = 0; i < kVertsPerLineSeg; ++i) {
502 (*vert)[i].fPos = (i < 2) ? a : b;
503 if (0 == i || 3 == i) {
504 (*vert)[i].fPos -= orthVec;
505 } else {
506 (*vert)[i].fPos += orthVec;
507 }
508 (*vert)[i].fLine.fA = normal.fX;
509 (*vert)[i].fLine.fB = normal.fY;
510 (*vert)[i].fLine.fC = lineC;
511 }
512 } else {
513 // just make it degenerate and likely offscreen
514 (*vert)[0].fPos.set(SK_ScalarMax, SK_ScalarMax);
515 (*vert)[1].fPos.set(SK_ScalarMax, SK_ScalarMax);
516 (*vert)[2].fPos.set(SK_ScalarMax, SK_ScalarMax);
517 (*vert)[3].fPos.set(SK_ScalarMax, SK_ScalarMax);
518 }
519
520 *vert += kVertsPerLineSeg;
521}
522
523}
524
525bool GrAAHairLinePathRenderer::createGeom(GrDrawTarget::StageBitfield stages) {
526
527 int rtHeight = fTarget->getRenderTarget()->height();
528
529 GrIRect clip;
530 if (fTarget->getClip().hasConservativeBounds()) {
531 GrRect clipRect = fTarget->getClip().getConservativeBounds();
532 clipRect.roundOut(&clip);
533 } else {
534 clip.setLargest();
535 }
536
537 if (stages == fPreviousStages &&
538 fPreviousViewMatrix == fTarget->getViewMatrix() &&
539 rtHeight == fPreviousRTHeight &&
540 fClipRect == clip) {
541 return true;
542 }
543
544 GrVertexLayout layout = GrDrawTarget::kEdge_VertexLayoutBit;
545 for (int s = 0; s < GrDrawTarget::kNumStages; ++s) {
546 if ((1 << s) & stages) {
547 layout |= GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(s);
548 }
549 }
550
551 GrMatrix viewM = fTarget->getViewMatrix();
552 viewM.preTranslate(fTranslate.fX, fTranslate.fY);
553
554 GrAlignedSTStorage<128, GrPoint> lineStorage;
555 GrAlignedSTStorage<128, GrPoint> quadStorage;
556 PtArray lines(&lineStorage);
557 PtArray quads(&quadStorage);
558 IntArray qSubdivs;
559 fQuadCnt = get_lines_and_quads(*fPath, viewM, clip,
560 &lines, &quads, &qSubdivs);
561
562 fLineSegmentCnt = lines.count() / 2;
563 int vertCnt = kVertsPerLineSeg * fLineSegmentCnt + kVertsPerQuad * fQuadCnt;
564
565 GrAssert(sizeof(Vertex) == GrDrawTarget::VertexSize(layout));
566
567 Vertex* verts;
568 if (!fTarget->reserveVertexSpace(layout, vertCnt, (void**)&verts)) {
569 return false;
570 }
571
572 for (int i = 0; i < fLineSegmentCnt; ++i) {
573 add_line(&lines[2*i], rtHeight, &verts);
574 }
575 int unsubdivQuadCnt = quads.count() / 3;
576 for (int i = 0; i < unsubdivQuadCnt; ++i) {
577 GrAssert(qSubdivs[i] >= 0);
578 add_quads(&quads[3*i], qSubdivs[i], &verts);
579 }
580
581 fPreviousStages = stages;
582 fPreviousViewMatrix = fTarget->getViewMatrix();
583 fPreviousRTHeight = rtHeight;
584 fClipRect = clip;
585 return true;
586}
587
588void GrAAHairLinePathRenderer::drawPath(GrDrawTarget::StageBitfield stages) {
589 GrDrawTarget::AutoStateRestore asr(fTarget);
590
591 GrMatrix ivm;
592
593 if (!this->createGeom(stages)) {
594 return;
595 }
596
597 if (fTarget->getViewInverse(&ivm)) {
598 fTarget->preConcatSamplerMatrices(stages, ivm);
599 }
600
601 fTarget->setViewMatrix(GrMatrix::I());
602
603 // TODO: See whether rendering lines as degenerate quads improves perf
604 // when we have a mix
605 fTarget->setIndexSourceToBuffer(fLinesIndexBuffer);
606 int lines = 0;
607 int nBufLines = fLinesIndexBuffer->maxQuads();
608 while (lines < fLineSegmentCnt) {
609 int n = GrMin(fLineSegmentCnt-lines, nBufLines);
610 fTarget->setVertexEdgeType(GrDrawTarget::kHairLine_EdgeType);
611 fTarget->drawIndexed(kTriangles_PrimitiveType,
612 kVertsPerLineSeg*lines, // startV
613 0, // startI
614 kVertsPerLineSeg*n, // vCount
615 kIdxsPerLineSeg*n); // iCount
616 lines += n;
617 }
618
619 fTarget->setIndexSourceToBuffer(fQuadsIndexBuffer);
620 int quads = 0;
621 while (quads < fQuadCnt) {
622 int n = GrMin(fQuadCnt-quads, kNumQuadsInIdxBuffer);
623 fTarget->setVertexEdgeType(GrDrawTarget::kHairQuad_EdgeType);
624 fTarget->drawIndexed(kTriangles_PrimitiveType,
625 4*fLineSegmentCnt + kVertsPerQuad*quads, // startV
626 0, // startI
627 kVertsPerQuad*n, // vCount
628 kIdxsPerQuad*n); // iCount
629 quads += n;
630 }
631
632}
633