blob: 8cf6c77fcfe03237c5303446aed87de8387f6f97 [file] [log] [blame]
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +00001
2/*
3 * Copyright 2012 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9#include "GrAAConvexPathRenderer.h"
10
11#include "GrContext.h"
12#include "GrDrawState.h"
13#include "GrPathUtils.h"
14#include "SkString.h"
15#include "SkTrace.h"
16
17
18GrAAConvexPathRenderer::GrAAConvexPathRenderer() {
19}
20
21bool GrAAConvexPathRenderer::canDrawPath(const GrDrawTarget::Caps& targetCaps,
22 const SkPath& path,
23 GrPathFill fill,
24 bool antiAlias) const {
25 return targetCaps.fShaderDerivativeSupport && antiAlias &&
26 kHairLine_PathFill != fill && !GrIsFillInverted(fill) &&
27 path.isConvex();
28}
29
30namespace {
31
bsalomon@google.com06809612012-01-21 15:03:39 +000032
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +000033struct Segment {
34 enum {
35 kLine,
36 kQuad
37 } fType;
bsalomon@google.com06809612012-01-21 15:03:39 +000038 // line uses a, quad uses a and b (first point comes from prev. segment)
39 GrPoint fA, fB;
40 // normal to edge ending at a and b
41 GrVec fANorm, fBNorm;
42 // mid vector at a that splits angle with previous edge
43 GrVec fPrevMid;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +000044};
45
46typedef SkTArray<Segment, true> SegmentArray;
47
48bool is_path_degenerate(const GrPath& path) {
49 int n = path.countPoints();
50 if (n < 3) {
51 return true;
52 }
bsalomon@google.com06809612012-01-21 15:03:39 +000053
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +000054 // compute a line from the first two points that are not equal, look for
55 // a third pt that is off the line.
56 static const SkScalar TOL = (SK_Scalar1 / 16);
57 bool foundLine = false;
58 GrPoint firstPoint = path.getPoint(0);
59 GrVec lineV;
60 SkScalar lineC;
61 int i = 1;
62
63 do {
64 GrPoint pt = path.getPoint(i);
65 if (!foundLine) {
66 if (pt != firstPoint) {
67 lineV = pt - firstPoint;
68 lineV.normalize();
69 lineV.setOrthog(lineV);
70 lineC = lineV.dot(firstPoint);
71 foundLine = true;
72 }
73 } else {
74 if (SkScalarAbs(lineV.dot(pt) - lineC) > TOL) {
75 return false;
76 }
77 }
78 ++i;
79 } while (i < n);
80 return true;
81}
82
83bool get_segments(const GrPath& path,
84 SegmentArray* segments,
bsalomon@google.com06809612012-01-21 15:03:39 +000085 int* quadCnt,
86 int* lineCnt) {
87 *quadCnt = 0;
88 *lineCnt = 0;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +000089 SkPath::Iter iter(path, true);
bsalomon@google.com5cc90d12012-01-17 16:28:34 +000090 // This renderer overemphasises very thin path regions. We use the distance
91 // to the path from the sample to compute coverage. Every pixel intersected
92 // by the path will be hit and the maximum distance is sqrt(2)/2. We don't
93 // notice that the sample may be close to a very thin area of the path and
94 // thus should be very light. This is particularly egregious for degenerate
95 // line paths. We detect paths that are very close to a line (zero area) and
96 // draw nothing.
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +000097 if (is_path_degenerate(path)) {
98 return false;
99 }
100 for (;;) {
101 GrPoint pts[4];
102 GrPathCmd cmd = (GrPathCmd)iter.next(pts);
103 switch (cmd) {
104 case kLine_PathCmd: {
105 segments->push_back();
106 segments->back().fType = Segment::kLine;
bsalomon@google.com06809612012-01-21 15:03:39 +0000107 segments->back().fA = pts[1];
108 ++(*lineCnt);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000109 break;
110 }
111 case kQuadratic_PathCmd:
112 segments->push_back();
113 segments->back().fType = Segment::kQuad;
bsalomon@google.com06809612012-01-21 15:03:39 +0000114 segments->back().fA = pts[1];
115 segments->back().fB = pts[2];
116 ++(*quadCnt);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000117 break;
118 case kCubic_PathCmd: {
119 SkSTArray<15, SkPoint, true> quads;
120 GrPathUtils::convertCubicToQuads(pts, SK_Scalar1, &quads);
121 int count = quads.count();
122 for (int q = 0; q < count; q += 3) {
123 segments->push_back();
124 segments->back().fType = Segment::kQuad;
bsalomon@google.com06809612012-01-21 15:03:39 +0000125 segments->back().fA = quads[q + 1];
126 segments->back().fB = quads[q + 2];
127 ++(*quadCnt);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000128 }
129 break;
130 };
131 case kEnd_PathCmd:
bsalomon@google.com06809612012-01-21 15:03:39 +0000132 GrAssert(*quadCnt + *lineCnt == segments->count());
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000133 return true;
134 default:
135 break;
136 }
137 }
138}
139
140struct QuadVertex {
141 GrPoint fPos;
bsalomon@google.com06809612012-01-21 15:03:39 +0000142 union {
143 GrPoint fQuadUV;
144 GrScalar fEdge[4];
145 };
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000146};
bsalomon@google.com06809612012-01-21 15:03:39 +0000147
148void get_counts(int quadCount, int lineCount, int* vCount, int* iCount) {
149 *vCount = 9 * lineCount + 11 * quadCount;
150 *iCount = 15 * lineCount + 24 * quadCount;
151}
152
153// This macro can be defined for visual debugging purposes. It exagerates the AA
154// smear at the edges by increasing the size of the extruded geometry used for
155// AA. However, the coverage value computed in the shader will still go to zero
156// at distance > .5 outside the curves. So, the shader code has be modified as
157// well to stretch out the AA smear.
158//#define STRETCH_AA
159#define STRETCH_FACTOR (20 * SK_Scalar1)
160
161void create_vertices(SegmentArray* segments,
162 const GrPoint& fanPt,
163 QuadVertex* verts,
164 uint16_t* idxs) {
165 int count = segments->count();
166 GrAssert(count > 1);
167 int prevS = count - 1;
168 const Segment& lastSeg = (*segments)[prevS];
169
170 // walk the segments and compute normals to each edge and
171 // bisectors at vertices. The loop relies on having the end point and normal
172 // from previous segment so we first compute that. Also, we determine
173 // whether normals point left or right to face outside the path.
174 GrVec prevPt;
175 GrPoint prevPrevPt;
176 GrVec prevNorm;
177 if (Segment::kLine == lastSeg.fType) {
178 prevPt = lastSeg.fA;
179 const Segment& secondLastSeg = (*segments)[prevS - 1];
180 prevPrevPt = (Segment::kLine == secondLastSeg.fType) ?
181 secondLastSeg.fA :
182 secondLastSeg.fB;
183 } else {
184 prevPt = lastSeg.fB;
185 prevPrevPt = lastSeg.fA;
186 }
187 GrVec::Side outside;
188 // we will compute our edge vectors so that they are pointing along the
189 // direction in which we are iterating the path. So here we take an opposite
190 // vector and get the side that the fan pt lies relative to it.
191 fanPt.distanceToLineBetweenSqd(prevPrevPt, prevPt, &outside);
192 prevNorm = prevPt - prevPrevPt;
193 prevNorm.normalize();
194 prevNorm.setOrthog(prevNorm, outside);
195#ifdef STRETCH_AA
196 prevNorm.scale(STRETCH_FACTOR);
197#endif
198
199 // compute the normals and bisectors
200 for (int s = 0; s < count; ++s, ++prevS) {
201 Segment& curr = (*segments)[s];
202
203 GrVec currVec = curr.fA - prevPt;
204 currVec.normalize();
205 curr.fANorm.setOrthog(currVec, outside);
206#ifdef STRETCH_AA
207 curr.fANorm.scale(STRETCH_FACTOR);
208#endif
209 curr.fPrevMid = prevNorm + curr.fANorm;
210 curr.fPrevMid.normalize();
211#ifdef STRETCH_AA
212 curr.fPrevMid.scale(STRETCH_FACTOR);
213#endif
214 if (Segment::kLine == curr.fType) {
215 prevPt = curr.fA;
216 prevNorm = curr.fANorm;
217 } else {
218 currVec = curr.fB - curr.fA;
219 currVec.normalize();
220 curr.fBNorm.setOrthog(currVec, outside);
221#ifdef STRETCH_AA
222 curr.fBNorm.scale(STRETCH_FACTOR);
223#endif
224 prevPt = curr.fB;
225 prevNorm = curr.fBNorm;
226 }
227 }
228
229 // compute the vertices / indices
230 if (Segment::kLine == lastSeg.fType) {
231 prevPt = lastSeg.fA;
232 prevNorm = lastSeg.fANorm;
233 } else {
234 prevPt = lastSeg.fB;
235 prevNorm = lastSeg.fBNorm;
236 }
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000237 int v = 0;
238 int i = 0;
bsalomon@google.com06809612012-01-21 15:03:39 +0000239 for (int s = 0; s < count; ++s, ++prevS) {
240 Segment& curr = (*segments)[s];
241 verts[v + 0].fPos = prevPt;
242 verts[v + 1].fPos = prevPt + prevNorm;
243 verts[v + 2].fPos = prevPt + curr.fPrevMid;
244 verts[v + 3].fPos = prevPt + curr.fANorm;
245 verts[v + 0].fQuadUV.set(0, 0);
246 verts[v + 1].fQuadUV.set(0, -SK_Scalar1);
247 verts[v + 2].fQuadUV.set(0, -SK_Scalar1);
248 verts[v + 3].fQuadUV.set(0, -SK_Scalar1);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000249
250 idxs[i + 0] = v + 0;
bsalomon@google.com06809612012-01-21 15:03:39 +0000251 idxs[i + 1] = v + 1;
252 idxs[i + 2] = v + 2;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000253 idxs[i + 3] = v + 0;
bsalomon@google.com06809612012-01-21 15:03:39 +0000254 idxs[i + 4] = v + 2;
255 idxs[i + 5] = v + 3;
256
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000257 v += 4;
258 i += 6;
259
bsalomon@google.com06809612012-01-21 15:03:39 +0000260 if (Segment::kLine == curr.fType) {
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000261 verts[v + 0].fPos = fanPt;
bsalomon@google.com06809612012-01-21 15:03:39 +0000262 verts[v + 1].fPos = prevPt;
263 verts[v + 2].fPos = curr.fA;
264 verts[v + 3].fPos = prevPt + curr.fANorm;
265 verts[v + 4].fPos = curr.fA + curr.fANorm;
266 GrScalar lineC = -curr.fANorm.dot(curr.fA);
267 GrScalar fanDist = curr.fANorm.dot(fanPt) - lineC;
268 verts[v + 0].fQuadUV.set(0, SkScalarAbs(fanDist));
269 verts[v + 1].fQuadUV.set(0, 0);
270 verts[v + 2].fQuadUV.set(0, 0);
271 verts[v + 3].fQuadUV.set(0, -GR_Scalar1);
272 verts[v + 4].fQuadUV.set(0, -GR_Scalar1);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000273
274 idxs[i + 0] = v + 0;
bsalomon@google.com495e2102012-01-21 14:48:36 +0000275 idxs[i + 1] = v + 1;
276 idxs[i + 2] = v + 2;
bsalomon@google.com06809612012-01-21 15:03:39 +0000277 idxs[i + 3] = v + 1;
bsalomon@google.com495e2102012-01-21 14:48:36 +0000278 idxs[i + 4] = v + 3;
bsalomon@google.com06809612012-01-21 15:03:39 +0000279 idxs[i + 5] = v + 4;
280 idxs[i + 6] = v + 1;
281 idxs[i + 7] = v + 4;
282 idxs[i + 8] = v + 2;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000283
bsalomon@google.com06809612012-01-21 15:03:39 +0000284 i += 9;
285 v += 5;
bsalomon@google.com495e2102012-01-21 14:48:36 +0000286
bsalomon@google.com06809612012-01-21 15:03:39 +0000287 prevPt = curr.fA;
288 prevNorm = curr.fANorm;
289 } else {
290 GrVec splitVec = curr.fANorm + curr.fBNorm;
291 splitVec.normalize();
292#ifdef STRETCH_AA
293 splitVec.scale(STRETCH_FACTOR);
294#endif
bsalomon@google.com495e2102012-01-21 14:48:36 +0000295
bsalomon@google.com06809612012-01-21 15:03:39 +0000296 verts[v + 0].fPos = prevPt;
297 verts[v + 1].fPos = curr.fA;
298 verts[v + 2].fPos = curr.fB;
299 verts[v + 3].fPos = fanPt;
300 verts[v + 4].fPos = prevPt + curr.fANorm;
301 verts[v + 5].fPos = curr.fA + splitVec;
302 verts[v + 6].fPos = curr.fB + curr.fBNorm;
303
304 verts[v + 0].fQuadUV.set(0, 0);
305 verts[v + 1].fQuadUV.set(GR_ScalarHalf, 0);
306 verts[v + 2].fQuadUV.set(GR_Scalar1, GR_Scalar1);
307 GrMatrix toUV;
308 GrPoint pts[] = {prevPt, curr.fA, curr.fB};
309 GrPathUtils::quadDesignSpaceToUVCoordsMatrix(pts, &toUV);
310 toUV.mapPointsWithStride(&verts[v + 3].fQuadUV,
311 &verts[v + 3].fPos,
312 sizeof(QuadVertex), 4);
313
314 idxs[i + 0] = v + 3;
315 idxs[i + 1] = v + 0;
316 idxs[i + 2] = v + 1;
317 idxs[i + 3] = v + 3;
318 idxs[i + 4] = v + 1;
319 idxs[i + 5] = v + 2;
320 idxs[i + 6] = v + 0;
321 idxs[i + 7] = v + 4;
322 idxs[i + 8] = v + 1;
323 idxs[i + 9] = v + 4;
324 idxs[i + 10] = v + 1;
325 idxs[i + 11] = v + 5;
326 idxs[i + 12] = v + 5;
327 idxs[i + 13] = v + 1;
328 idxs[i + 14] = v + 2;
329 idxs[i + 15] = v + 5;
330 idxs[i + 16] = v + 2;
331 idxs[i + 17] = v + 6;
332
333 i += 18;
334 v += 7;
335 prevPt = curr.fB;
336 prevNorm = curr.fBNorm;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000337 }
338 }
339}
340
341}
342
343void GrAAConvexPathRenderer::drawPath(GrDrawState::StageMask stageMask) {
344 GrAssert(fPath->isConvex());
345 if (fPath->isEmpty()) {
346 return;
347 }
348 GrDrawState* drawState = fTarget->drawState();
349
350 GrDrawTarget::AutoStateRestore asr;
351 GrMatrix vm = drawState->getViewMatrix();
352 vm.postTranslate(fTranslate.fX, fTranslate.fY);
353 asr.set(fTarget);
354 GrMatrix ivm;
355 if (vm.invert(&ivm)) {
356 drawState->preConcatSamplerMatrices(stageMask, ivm);
357 }
358 drawState->setViewMatrix(GrMatrix::I());
359
bsalomon@google.com06809612012-01-21 15:03:39 +0000360
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000361 SkPath path;
362 fPath->transform(vm, &path);
363
bsalomon@google.com06809612012-01-21 15:03:39 +0000364 SkPoint fanPt = {path.getBounds().centerX(),
365 path.getBounds().centerY()};
366
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000367 GrVertexLayout layout = 0;
368 for (int s = 0; s < GrDrawState::kNumStages; ++s) {
369 if ((1 << s) & stageMask) {
370 layout |= GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(s);
371 }
372 }
373 layout |= GrDrawTarget::kEdge_VertexLayoutBit;
374
375 QuadVertex *verts;
376 uint16_t* idxs;
377
bsalomon@google.com06809612012-01-21 15:03:39 +0000378 int nQuads;
379 int nLines;
bsalomon@google.com495e2102012-01-21 14:48:36 +0000380 SegmentArray segments;
bsalomon@google.com06809612012-01-21 15:03:39 +0000381 if (!get_segments(path, &segments, &nQuads, &nLines)) {
bsalomon@google.com495e2102012-01-21 14:48:36 +0000382 return;
383 }
bsalomon@google.com06809612012-01-21 15:03:39 +0000384 int vCount;
385 int iCount;
386 get_counts(nQuads, nLines, &vCount, &iCount);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000387
388 if (!fTarget->reserveVertexSpace(layout,
389 vCount,
390 reinterpret_cast<void**>(&verts))) {
391 return;
392 }
393 if (!fTarget->reserveIndexSpace(iCount, reinterpret_cast<void**>(&idxs))) {
394 fTarget->resetVertexSource();
395 return;
396 }
397
bsalomon@google.com06809612012-01-21 15:03:39 +0000398 create_vertices(&segments, fanPt, verts, idxs);
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000399
400 drawState->setVertexEdgeType(GrDrawState::kQuad_EdgeType);
401 fTarget->drawIndexed(kTriangles_PrimitiveType,
402 0, // start vertex
403 0, // start index
404 vCount,
405 iCount);
406}
407