blob: 1e445fe60cdcdeead53901b04fb82ab63a7358bb [file] [log] [blame]
bsalomon@google.comf75b84e2011-09-29 14:58:28 +00001/*
2 * Copyright 2011 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
bsalomon@google.comaeb21602011-08-30 18:13:44 +00008#include "GrAAHairLinePathRenderer.h"
9
10#include "GrContext.h"
tomhudson@google.com93813632011-10-27 20:21:16 +000011#include "GrDrawState.h"
bsalomon@google.comc26d94f2013-03-25 18:19:00 +000012#include "GrDrawTargetCaps.h"
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +000013#include "GrEffect.h"
bsalomon@google.comaeb21602011-08-30 18:13:44 +000014#include "GrGpu.h"
15#include "GrIndexBuffer.h"
bsalomon@google.comdbeeac32011-09-12 14:59:34 +000016#include "GrPathUtils.h"
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +000017#include "GrTBackendEffectFactory.h"
bsalomon@google.comaeb21602011-08-30 18:13:44 +000018#include "SkGeometry.h"
sugoi@google.com12b4e272012-12-06 20:13:11 +000019#include "SkStroke.h"
bsalomon@google.comaeb21602011-08-30 18:13:44 +000020#include "SkTemplates.h"
21
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +000022#include "gl/GrGLEffect.h"
23#include "gl/GrGLSL.h"
bsalomon@google.com4647f902013-03-26 14:45:27 +000024
bsalomon@google.comaeb21602011-08-30 18:13:44 +000025namespace {
26// quadratics are rendered as 5-sided polys in order to bound the
27// AA stroke around the center-curve. See comments in push_quad_index_buffer and
egdaniel@google.com5383a752013-07-12 20:15:34 +000028// bloat_quad. Quadratics and conics share an index buffer
bsalomon@google.comaeb21602011-08-30 18:13:44 +000029static const int kVertsPerQuad = 5;
30static const int kIdxsPerQuad = 9;
31
jvanverth@google.com681ccf02013-08-16 14:51:51 +000032static const int kVertsPerLineSeg = 6;
33static const int kIdxsPerLineSeg = 12;
bsalomon@google.comaeb21602011-08-30 18:13:44 +000034
35static const int kNumQuadsInIdxBuffer = 256;
36static const size_t kQuadIdxSBufize = kIdxsPerQuad *
37 sizeof(uint16_t) *
38 kNumQuadsInIdxBuffer;
39
jvanverth@google.com681ccf02013-08-16 14:51:51 +000040static const int kNumLineSegsInIdxBuffer = 256;
41static const size_t kLineSegIdxSBufize = kIdxsPerLineSeg *
42 sizeof(uint16_t) *
43 kNumLineSegsInIdxBuffer;
44
45static bool push_quad_index_data(GrIndexBuffer* qIdxBuffer) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +000046 uint16_t* data = (uint16_t*) qIdxBuffer->lock();
47 bool tempData = NULL == data;
48 if (tempData) {
tomhudson@google.comc377baf2012-07-09 20:17:56 +000049 data = SkNEW_ARRAY(uint16_t, kNumQuadsInIdxBuffer * kIdxsPerQuad);
bsalomon@google.comaeb21602011-08-30 18:13:44 +000050 }
51 for (int i = 0; i < kNumQuadsInIdxBuffer; ++i) {
52
53 // Each quadratic is rendered as a five sided polygon. This poly bounds
54 // the quadratic's bounding triangle but has been expanded so that the
55 // 1-pixel wide area around the curve is inside the poly.
56 // If a,b,c are the original control points then the poly a0,b0,c0,c1,a1
57 // that is rendered would look like this:
58 // b0
59 // b
60 //
61 // a0 c0
62 // a c
63 // a1 c1
bsalomon@google.com0e5104c2012-04-10 16:20:41 +000064 // Each is drawn as three triangles specified by these 9 indices:
bsalomon@google.comaeb21602011-08-30 18:13:44 +000065 int baseIdx = i * kIdxsPerQuad;
66 uint16_t baseVert = (uint16_t)(i * kVertsPerQuad);
67 data[0 + baseIdx] = baseVert + 0; // a0
68 data[1 + baseIdx] = baseVert + 1; // a1
69 data[2 + baseIdx] = baseVert + 2; // b0
70 data[3 + baseIdx] = baseVert + 2; // b0
71 data[4 + baseIdx] = baseVert + 4; // c1
72 data[5 + baseIdx] = baseVert + 3; // c0
73 data[6 + baseIdx] = baseVert + 1; // a1
74 data[7 + baseIdx] = baseVert + 4; // c1
75 data[8 + baseIdx] = baseVert + 2; // b0
76 }
77 if (tempData) {
78 bool ret = qIdxBuffer->updateData(data, kQuadIdxSBufize);
79 delete[] data;
80 return ret;
81 } else {
82 qIdxBuffer->unlock();
83 return true;
84 }
85}
jvanverth@google.com681ccf02013-08-16 14:51:51 +000086
87static bool push_line_index_data(GrIndexBuffer* lIdxBuffer) {
88 uint16_t* data = (uint16_t*) lIdxBuffer->lock();
89 bool tempData = NULL == data;
90 if (tempData) {
91 data = SkNEW_ARRAY(uint16_t, kNumLineSegsInIdxBuffer * kIdxsPerLineSeg);
92 }
93 for (int i = 0; i < kNumLineSegsInIdxBuffer; ++i) {
94 // Each line segment is rendered as two quads, with alpha = 1 along the
95 // spine of the segment, and alpha = 0 along the outer edges, represented
96 // horizontally (i.e., the line equation is t*(p1-p0) + p0)
97 //
98 // p4 p5
99 // p0 p1
100 // p2 p3
101 //
102 // Each is drawn as four triangles specified by these 12 indices:
103 int baseIdx = i * kIdxsPerLineSeg;
104 uint16_t baseVert = (uint16_t)(i * kVertsPerLineSeg);
105 data[0 + baseIdx] = baseVert + 0; // p0
106 data[1 + baseIdx] = baseVert + 1; // p1
107 data[2 + baseIdx] = baseVert + 2; // p2
108
109 data[3 + baseIdx] = baseVert + 2; // p2
110 data[4 + baseIdx] = baseVert + 1; // p1
111 data[5 + baseIdx] = baseVert + 3; // p3
112
113 data[6 + baseIdx] = baseVert + 0; // p0
114 data[7 + baseIdx] = baseVert + 5; // p5
115 data[8 + baseIdx] = baseVert + 1; // p1
116
117 data[9 + baseIdx] = baseVert + 0; // p0
118 data[10+ baseIdx] = baseVert + 4; // p4
119 data[11+ baseIdx] = baseVert + 5; // p5
120 }
121 if (tempData) {
122 bool ret = lIdxBuffer->updateData(data, kLineSegIdxSBufize);
123 delete[] data;
124 return ret;
125 } else {
126 lIdxBuffer->unlock();
127 return true;
128 }
129}
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000130}
131
132GrPathRenderer* GrAAHairLinePathRenderer::Create(GrContext* context) {
bsalomon@google.coma8a6a322011-09-23 14:19:58 +0000133 GrGpu* gpu = context->getGpu();
134 GrIndexBuffer* qIdxBuf = gpu->createIndexBuffer(kQuadIdxSBufize, false);
135 SkAutoTUnref<GrIndexBuffer> qIdxBuffer(qIdxBuf);
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000136 if (NULL == qIdxBuf || !push_quad_index_data(qIdxBuf)) {
137 return NULL;
138 }
139 GrIndexBuffer* lIdxBuf = gpu->createIndexBuffer(kLineSegIdxSBufize, false);
140 SkAutoTUnref<GrIndexBuffer> lIdxBuffer(lIdxBuf);
141 if (NULL == lIdxBuf || !push_line_index_data(lIdxBuf)) {
bsalomon@google.coma8a6a322011-09-23 14:19:58 +0000142 return NULL;
143 }
tomhudson@google.comc377baf2012-07-09 20:17:56 +0000144 return SkNEW_ARGS(GrAAHairLinePathRenderer,
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000145 (context, lIdxBuf, qIdxBuf));
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000146}
147
148GrAAHairLinePathRenderer::GrAAHairLinePathRenderer(
149 const GrContext* context,
150 const GrIndexBuffer* linesIndexBuffer,
151 const GrIndexBuffer* quadsIndexBuffer) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000152 fLinesIndexBuffer = linesIndexBuffer;
153 linesIndexBuffer->ref();
154 fQuadsIndexBuffer = quadsIndexBuffer;
155 quadsIndexBuffer->ref();
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000156}
157
158GrAAHairLinePathRenderer::~GrAAHairLinePathRenderer() {
159 fLinesIndexBuffer->unref();
160 fQuadsIndexBuffer->unref();
161}
162
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000163namespace {
164
bsalomon@google.com92669012011-09-27 19:10:05 +0000165#define PREALLOC_PTARRAY(N) SkSTArray<(N),SkPoint, true>
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000166
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000167// Takes 178th time of logf on Z600 / VC2010
168int get_float_exp(float x) {
169 GR_STATIC_ASSERT(sizeof(int) == sizeof(float));
170#if GR_DEBUG
171 static bool tested;
172 if (!tested) {
173 tested = true;
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000174 SkASSERT(get_float_exp(0.25f) == -2);
175 SkASSERT(get_float_exp(0.3f) == -2);
176 SkASSERT(get_float_exp(0.5f) == -1);
177 SkASSERT(get_float_exp(1.f) == 0);
178 SkASSERT(get_float_exp(2.f) == 1);
179 SkASSERT(get_float_exp(2.5f) == 1);
180 SkASSERT(get_float_exp(8.f) == 3);
181 SkASSERT(get_float_exp(100.f) == 6);
182 SkASSERT(get_float_exp(1000.f) == 9);
183 SkASSERT(get_float_exp(1024.f) == 10);
184 SkASSERT(get_float_exp(3000000.f) == 21);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000185 }
186#endif
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000187 const int* iptr = (const int*)&x;
188 return (((*iptr) & 0x7f800000) >> 23) - 127;
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000189}
190
egdaniel@google.com5383a752013-07-12 20:15:34 +0000191// Uses the max curvature function for quads to estimate
192// where to chop the conic. If the max curvature is not
193// found along the curve segment it will return 1 and
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000194// dst[0] is the original conic. If it returns 2 the dst[0]
egdaniel@google.com5383a752013-07-12 20:15:34 +0000195// and dst[1] are the two new conics.
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000196int split_conic(const SkPoint src[3], SkConic dst[2], const SkScalar weight) {
egdaniel@google.com5383a752013-07-12 20:15:34 +0000197 SkScalar t = SkFindQuadMaxCurvature(src);
198 if (t == 0) {
199 if (dst) {
200 dst[0].set(src, weight);
201 }
202 return 1;
203 } else {
204 if (dst) {
205 SkConic conic;
206 conic.set(src, weight);
207 conic.chopAt(t, dst);
208 }
209 return 2;
210 }
211}
212
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000213// Calls split_conic on the entire conic and then once more on each subsection.
214// Most cases will result in either 1 conic (chop point is not within t range)
215// or 3 points (split once and then one subsection is split again).
216int chop_conic(const SkPoint src[3], SkConic dst[4], const SkScalar weight) {
217 SkConic dstTemp[2];
218 int conicCnt = split_conic(src, dstTemp, weight);
219 if (2 == conicCnt) {
220 int conicCnt2 = split_conic(dstTemp[0].fPts, dst, dstTemp[0].fW);
221 conicCnt = conicCnt2 + split_conic(dstTemp[1].fPts, &dst[conicCnt2], dstTemp[1].fW);
222 } else {
223 dst[0] = dstTemp[0];
224 }
225 return conicCnt;
226}
227
egdaniel@google.com5383a752013-07-12 20:15:34 +0000228// returns 0 if quad/conic is degen or close to it
229// in this case approx the path with lines
230// otherwise returns 1
231int is_degen_quad_or_conic(const SkPoint p[3]) {
232 static const SkScalar gDegenerateToLineTol = SK_Scalar1;
233 static const SkScalar gDegenerateToLineTolSqd =
234 SkScalarMul(gDegenerateToLineTol, gDegenerateToLineTol);
235
236 if (p[0].distanceToSqd(p[1]) < gDegenerateToLineTolSqd ||
237 p[1].distanceToSqd(p[2]) < gDegenerateToLineTolSqd) {
238 return 1;
239 }
240
241 SkScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]);
242 if (dsqd < gDegenerateToLineTolSqd) {
243 return 1;
244 }
245
246 if (p[2].distanceToLineBetweenSqd(p[1], p[0]) < gDegenerateToLineTolSqd) {
247 return 1;
248 }
249 return 0;
250}
251
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000252// we subdivide the quads to avoid huge overfill
253// if it returns -1 then should be drawn as lines
254int num_quad_subdivs(const SkPoint p[3]) {
255 static const SkScalar gDegenerateToLineTol = SK_Scalar1;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000256 static const SkScalar gDegenerateToLineTolSqd =
bsalomon@google.com46a2a1e2011-09-06 22:10:52 +0000257 SkScalarMul(gDegenerateToLineTol, gDegenerateToLineTol);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000258
bsalomon@google.com46a2a1e2011-09-06 22:10:52 +0000259 if (p[0].distanceToSqd(p[1]) < gDegenerateToLineTolSqd ||
260 p[1].distanceToSqd(p[2]) < gDegenerateToLineTolSqd) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000261 return -1;
262 }
bsalomon@google.com46a2a1e2011-09-06 22:10:52 +0000263
bsalomon@google.com81712882012-11-01 17:12:34 +0000264 SkScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]);
bsalomon@google.com46a2a1e2011-09-06 22:10:52 +0000265 if (dsqd < gDegenerateToLineTolSqd) {
266 return -1;
267 }
268
269 if (p[2].distanceToLineBetweenSqd(p[1], p[0]) < gDegenerateToLineTolSqd) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000270 return -1;
271 }
272
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000273 // tolerance of triangle height in pixels
274 // tuned on windows Quadro FX 380 / Z600
275 // trade off of fill vs cpu time on verts
276 // maybe different when do this using gpu (geo or tess shaders)
277 static const SkScalar gSubdivTol = 175 * SK_Scalar1;
278
robertphillips@google.com7460b372012-04-25 16:54:51 +0000279 if (dsqd <= SkScalarMul(gSubdivTol, gSubdivTol)) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000280 return 0;
281 } else {
robertphillips@google.com87379e12013-03-29 12:11:10 +0000282 static const int kMaxSub = 4;
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000283 // subdividing the quad reduces d by 4. so we want x = log4(d/tol)
284 // = log4(d*d/tol*tol)/2
285 // = log2(d*d/tol*tol)
286
287#ifdef SK_SCALAR_IS_FLOAT
288 // +1 since we're ignoring the mantissa contribution.
289 int log = get_float_exp(dsqd/(gSubdivTol*gSubdivTol)) + 1;
290 log = GrMin(GrMax(0, log), kMaxSub);
291 return log;
292#else
robertphillips@google.com7460b372012-04-25 16:54:51 +0000293 SkScalar log = SkScalarLog(
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000294 SkScalarDiv(dsqd,
robertphillips@google.com7460b372012-04-25 16:54:51 +0000295 SkScalarMul(gSubdivTol, gSubdivTol)));
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000296 static const SkScalar conv = SkScalarInvert(SkScalarLog(2));
297 log = SkScalarMul(log, conv);
298 return GrMin(GrMax(0, SkScalarCeilToInt(log)),kMaxSub);
299#endif
300 }
301}
302
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000303/**
304 * Generates the lines and quads to be rendered. Lines are always recorded in
305 * device space. We will do a device space bloat to account for the 1pixel
306 * thickness.
307 * Quads are recorded in device space unless m contains
308 * perspective, then in they are in src space. We do this because we will
309 * subdivide large quads to reduce over-fill. This subdivision has to be
310 * performed before applying the perspective matrix.
311 */
312int generate_lines_and_quads(const SkPath& path,
313 const SkMatrix& m,
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000314 const SkIRect& devClipBounds,
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000315 GrAAHairLinePathRenderer::PtArray* lines,
316 GrAAHairLinePathRenderer::PtArray* quads,
317 GrAAHairLinePathRenderer::PtArray* conics,
318 GrAAHairLinePathRenderer::IntArray* quadSubdivCnts,
319 GrAAHairLinePathRenderer::FloatArray* conicWeights) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000320 SkPath::Iter iter(path, false);
321
322 int totalQuadCount = 0;
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000323 SkRect bounds;
324 SkIRect ibounds;
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000325
326 bool persp = m.hasPerspective();
327
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000328 for (;;) {
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000329 GrPoint pathPts[4];
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000330 GrPoint devPts[4];
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000331 SkPath::Verb verb = iter.next(pathPts);
bsalomon@google.com94b284d2013-05-10 17:14:06 +0000332 switch (verb) {
egdaniel@google.com5383a752013-07-12 20:15:34 +0000333 case SkPath::kConic_Verb: {
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000334 SkConic dst[4];
335 // We chop the conics to create tighter clipping to hide error
336 // that appears near max curvature of very thin conics. Thin
337 // hyperbolas with high weight still show error.
egdaniel@google.com5383a752013-07-12 20:15:34 +0000338 int conicCnt = chop_conic(pathPts, dst, iter.conicWeight());
339 for (int i = 0; i < conicCnt; ++i) {
340 SkPoint* chopPnts = dst[i].fPts;
341 m.mapPoints(devPts, chopPnts, 3);
342 bounds.setBounds(devPts, 3);
343 bounds.outset(SK_Scalar1, SK_Scalar1);
344 bounds.roundOut(&ibounds);
345 if (SkIRect::Intersects(devClipBounds, ibounds)) {
346 if (is_degen_quad_or_conic(devPts)) {
347 SkPoint* pts = lines->push_back_n(4);
348 pts[0] = devPts[0];
349 pts[1] = devPts[1];
350 pts[2] = devPts[1];
351 pts[3] = devPts[2];
352 } else {
353 // when in perspective keep conics in src space
354 SkPoint* cPts = persp ? chopPnts : devPts;
355 SkPoint* pts = conics->push_back_n(3);
356 pts[0] = cPts[0];
357 pts[1] = cPts[1];
358 pts[2] = cPts[2];
359 conicWeights->push_back() = dst[i].fW;
360 }
361 }
362 }
reed@google.com277c3f82013-05-31 15:17:50 +0000363 break;
egdaniel@google.com5383a752013-07-12 20:15:34 +0000364 }
365 case SkPath::kMove_Verb:
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000366 break;
bsalomon@google.com94b284d2013-05-10 17:14:06 +0000367 case SkPath::kLine_Verb:
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000368 m.mapPoints(devPts, pathPts, 2);
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000369 bounds.setBounds(devPts, 2);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000370 bounds.outset(SK_Scalar1, SK_Scalar1);
371 bounds.roundOut(&ibounds);
robertphillips@google.com7b112892012-07-31 15:18:21 +0000372 if (SkIRect::Intersects(devClipBounds, ibounds)) {
bsalomon@google.coma996fec2011-09-13 18:49:13 +0000373 SkPoint* pts = lines->push_back_n(2);
374 pts[0] = devPts[0];
375 pts[1] = devPts[1];
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000376 }
377 break;
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000378 case SkPath::kQuad_Verb: {
379 SkPoint choppedPts[5];
380 // Chopping the quad helps when the quad is either degenerate or nearly degenerate.
381 // When it is degenerate it allows the approximation with lines to work since the
382 // chop point (if there is one) will be at the parabola's vertex. In the nearly
383 // degenerate the QuadUVMatrix computed for the points is almost singular which
384 // can cause rendering artifacts.
385 int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts);
386 for (int i = 0; i < n; ++i) {
387 SkPoint* quadPts = choppedPts + i * 2;
388 m.mapPoints(devPts, quadPts, 3);
389 bounds.setBounds(devPts, 3);
390 bounds.outset(SK_Scalar1, SK_Scalar1);
391 bounds.roundOut(&ibounds);
392
393 if (SkIRect::Intersects(devClipBounds, ibounds)) {
394 int subdiv = num_quad_subdivs(devPts);
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000395 SkASSERT(subdiv >= -1);
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000396 if (-1 == subdiv) {
397 SkPoint* pts = lines->push_back_n(4);
398 pts[0] = devPts[0];
399 pts[1] = devPts[1];
400 pts[2] = devPts[1];
401 pts[3] = devPts[2];
402 } else {
403 // when in perspective keep quads in src space
404 SkPoint* qPts = persp ? quadPts : devPts;
405 SkPoint* pts = quads->push_back_n(3);
406 pts[0] = qPts[0];
407 pts[1] = qPts[1];
408 pts[2] = qPts[2];
409 quadSubdivCnts->push_back() = subdiv;
410 totalQuadCount += 1 << subdiv;
411 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000412 }
413 }
bsalomon@google.coma51ab842012-07-10 19:53:34 +0000414 break;
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000415 }
bsalomon@google.com94b284d2013-05-10 17:14:06 +0000416 case SkPath::kCubic_Verb:
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000417 m.mapPoints(devPts, pathPts, 4);
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000418 bounds.setBounds(devPts, 4);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000419 bounds.outset(SK_Scalar1, SK_Scalar1);
420 bounds.roundOut(&ibounds);
robertphillips@google.com7b112892012-07-31 15:18:21 +0000421 if (SkIRect::Intersects(devClipBounds, ibounds)) {
bsalomon@google.com92669012011-09-27 19:10:05 +0000422 PREALLOC_PTARRAY(32) q;
bsalomon@google.coma51ab842012-07-10 19:53:34 +0000423 // we don't need a direction if we aren't constraining the subdivision
424 static const SkPath::Direction kDummyDir = SkPath::kCCW_Direction;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000425 // We convert cubics to quadratics (for now).
426 // In perspective have to do conversion in src space.
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000427 if (persp) {
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000428 SkScalar tolScale =
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000429 GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m,
430 path.getBounds());
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000431 GrPathUtils::convertCubicToQuads(pathPts, tolScale, false, kDummyDir, &q);
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000432 } else {
bsalomon@google.coma51ab842012-07-10 19:53:34 +0000433 GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, false, kDummyDir, &q);
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000434 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000435 for (int i = 0; i < q.count(); i += 3) {
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000436 SkPoint* qInDevSpace;
437 // bounds has to be calculated in device space, but q is
438 // in src space when there is perspective.
439 if (persp) {
440 m.mapPoints(devPts, &q[i], 3);
441 bounds.setBounds(devPts, 3);
442 qInDevSpace = devPts;
443 } else {
444 bounds.setBounds(&q[i], 3);
445 qInDevSpace = &q[i];
446 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000447 bounds.outset(SK_Scalar1, SK_Scalar1);
448 bounds.roundOut(&ibounds);
robertphillips@google.com7b112892012-07-31 15:18:21 +0000449 if (SkIRect::Intersects(devClipBounds, ibounds)) {
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000450 int subdiv = num_quad_subdivs(qInDevSpace);
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000451 SkASSERT(subdiv >= -1);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000452 if (-1 == subdiv) {
bsalomon@google.coma996fec2011-09-13 18:49:13 +0000453 SkPoint* pts = lines->push_back_n(4);
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000454 // lines should always be in device coords
bsalomon@google.coma996fec2011-09-13 18:49:13 +0000455 pts[0] = qInDevSpace[0];
456 pts[1] = qInDevSpace[1];
457 pts[2] = qInDevSpace[1];
458 pts[3] = qInDevSpace[2];
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000459 } else {
bsalomon@google.coma996fec2011-09-13 18:49:13 +0000460 SkPoint* pts = quads->push_back_n(3);
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000461 // q is already in src space when there is no
462 // perspective and dev coords otherwise.
bsalomon@google.coma996fec2011-09-13 18:49:13 +0000463 pts[0] = q[0 + i];
464 pts[1] = q[1 + i];
465 pts[2] = q[2 + i];
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000466 quadSubdivCnts->push_back() = subdiv;
467 totalQuadCount += 1 << subdiv;
468 }
469 }
470 }
471 }
bsalomon@google.coma51ab842012-07-10 19:53:34 +0000472 break;
bsalomon@google.com94b284d2013-05-10 17:14:06 +0000473 case SkPath::kClose_Verb:
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000474 break;
bsalomon@google.com94b284d2013-05-10 17:14:06 +0000475 case SkPath::kDone_Verb:
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000476 return totalQuadCount;
477 }
478 }
479}
480
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000481struct LineVertex {
482 GrPoint fPos;
483 GrColor fCoverage;
484};
485
486struct BezierVertex {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000487 GrPoint fPos;
488 union {
489 struct {
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000490 SkScalar fK;
491 SkScalar fL;
492 SkScalar fM;
egdaniel@google.com5383a752013-07-12 20:15:34 +0000493 } fConic;
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000494 GrVec fQuadCoord;
495 struct {
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000496 SkScalar fBogus[4];
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000497 };
498 };
499};
egdaniel@google.com5383a752013-07-12 20:15:34 +0000500
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000501GR_STATIC_ASSERT(sizeof(BezierVertex) == 3 * sizeof(GrPoint));
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000502
503void intersect_lines(const SkPoint& ptA, const SkVector& normA,
504 const SkPoint& ptB, const SkVector& normB,
505 SkPoint* result) {
506
507 SkScalar lineAW = -normA.dot(ptA);
508 SkScalar lineBW = -normB.dot(ptB);
509
510 SkScalar wInv = SkScalarMul(normA.fX, normB.fY) -
egdaniel@google.com5383a752013-07-12 20:15:34 +0000511 SkScalarMul(normA.fY, normB.fX);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000512 wInv = SkScalarInvert(wInv);
513
514 result->fX = SkScalarMul(normA.fY, lineBW) - SkScalarMul(lineAW, normB.fY);
515 result->fX = SkScalarMul(result->fX, wInv);
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000516
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000517 result->fY = SkScalarMul(lineAW, normB.fX) - SkScalarMul(normA.fX, lineBW);
518 result->fY = SkScalarMul(result->fY, wInv);
519}
520
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000521void set_uv_quad(const SkPoint qpts[3], BezierVertex verts[kVertsPerQuad]) {
egdaniel@google.com34b05ca2013-08-05 20:43:12 +0000522 // this should be in the src space, not dev coords, when we have perspective
523 GrPathUtils::QuadUVMatrix DevToUV(qpts);
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000524 DevToUV.apply<kVertsPerQuad, sizeof(BezierVertex), sizeof(GrPoint)>(verts);
egdaniel@google.com34b05ca2013-08-05 20:43:12 +0000525}
526
bsalomon@google.comb9086a02012-11-01 18:02:54 +0000527void bloat_quad(const SkPoint qpts[3], const SkMatrix* toDevice,
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000528 const SkMatrix* toSrc, BezierVertex verts[kVertsPerQuad],
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +0000529 SkRect* devBounds) {
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000530 SkASSERT(!toDevice == !toSrc);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000531 // original quad is specified by tri a,b,c
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000532 SkPoint a = qpts[0];
533 SkPoint b = qpts[1];
534 SkPoint c = qpts[2];
535
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000536 if (toDevice) {
537 toDevice->mapPoints(&a, 1);
538 toDevice->mapPoints(&b, 1);
539 toDevice->mapPoints(&c, 1);
540 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000541 // make a new poly where we replace a and c by a 1-pixel wide edges orthog
542 // to edges ab and bc:
543 //
544 // before | after
545 // | b0
546 // b |
547 // |
548 // | a0 c0
549 // a c | a1 c1
550 //
551 // edges a0->b0 and b0->c0 are parallel to original edges a->b and b->c,
552 // respectively.
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000553 BezierVertex& a0 = verts[0];
554 BezierVertex& a1 = verts[1];
555 BezierVertex& b0 = verts[2];
556 BezierVertex& c0 = verts[3];
557 BezierVertex& c1 = verts[4];
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000558
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000559 SkVector ab = b;
560 ab -= a;
561 SkVector ac = c;
562 ac -= a;
563 SkVector cb = b;
564 cb -= c;
565
566 // We should have already handled degenerates
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000567 SkASSERT(ab.length() > 0 && cb.length() > 0);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000568
569 ab.normalize();
570 SkVector abN;
571 abN.setOrthog(ab, SkVector::kLeft_Side);
572 if (abN.dot(ac) > 0) {
573 abN.negate();
574 }
575
576 cb.normalize();
577 SkVector cbN;
578 cbN.setOrthog(cb, SkVector::kLeft_Side);
579 if (cbN.dot(ac) < 0) {
580 cbN.negate();
581 }
582
583 a0.fPos = a;
584 a0.fPos += abN;
585 a1.fPos = a;
586 a1.fPos -= abN;
587
588 c0.fPos = c;
589 c0.fPos += cbN;
590 c1.fPos = c;
591 c1.fPos -= cbN;
592
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +0000593 // This point may not be within 1 pixel of a control point. We update the bounding box to
594 // include it.
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000595 intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos);
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +0000596 devBounds->growToInclude(b0.fPos.fX, b0.fPos.fY);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000597
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000598 if (toSrc) {
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000599 toSrc->mapPointsWithStride(&verts[0].fPos, sizeof(BezierVertex), kVertsPerQuad);
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000600 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000601}
602
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000603// Input:
604// Three control points: p[0], p[1], p[2] and weight: w
605// Output:
606// Let:
607// l = (2*w * (y1 - y0), 2*w * (x0 - x1), 2*w * (x1*y0 - x0*y1))
608// m = (2*w * (y2 - y1), 2*w * (x1 - x2), 2*w * (x2*y1 - x1*y2))
609// k = (y2 - y0, x0 - x2, (x2 - x0)*y0 - (y2 - y0)*x0 )
610void calc_conic_klm(const SkPoint p[3], const SkScalar weight,
611 SkScalar k[3], SkScalar l[3], SkScalar m[3]) {
612 const SkScalar w2 = 2 * weight;
613 l[0] = w2 * (p[1].fY - p[0].fY);
614 l[1] = w2 * (p[0].fX - p[1].fX);
615 l[2] = w2 * (p[1].fX * p[0].fY - p[0].fX * p[1].fY);
egdaniel@google.com5383a752013-07-12 20:15:34 +0000616
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000617 m[0] = w2 * (p[2].fY - p[1].fY);
618 m[1] = w2 * (p[1].fX - p[2].fX);
619 m[2] = w2 * (p[2].fX * p[1].fY - p[1].fX * p[2].fY);
620
621 k[0] = p[2].fY - p[0].fY;
622 k[1] = p[0].fX - p[2].fX;
623 k[2] = (p[2].fX - p[0].fX) * p[0].fY - (p[2].fY - p[0].fY) * p[0].fX;
624
625 // scale the max absolute value of coeffs to 10
626 SkScalar scale = 0.0f;
627 for (int i = 0; i < 3; ++i) {
628 scale = SkMaxScalar(scale, SkScalarAbs(k[i]));
629 scale = SkMaxScalar(scale, SkScalarAbs(l[i]));
630 scale = SkMaxScalar(scale, SkScalarAbs(m[i]));
631 }
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000632 SkASSERT(scale > 0);
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000633 scale /= 10.0f;
634 k[0] /= scale;
635 k[1] /= scale;
636 k[2] /= scale;
637 l[0] /= scale;
638 l[1] /= scale;
639 l[2] /= scale;
640 m[0] /= scale;
641 m[1] /= scale;
642 m[2] /= scale;
643}
644
645// Equations based off of Loop-Blinn Quadratic GPU Rendering
egdaniel@google.com5383a752013-07-12 20:15:34 +0000646// Input Parametric:
647// P(t) = (P0*(1-t)^2 + 2*w*P1*t*(1-t) + P2*t^2) / (1-t)^2 + 2*w*t*(1-t) + t^2)
648// Output Implicit:
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000649// f(x, y, w) = f(P) = K^2 - LM
650// K = dot(k, P), L = dot(l, P), M = dot(m, P)
651// k, l, m are calculated in function calc_conic_klm
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000652void set_conic_coeffs(const SkPoint p[3], BezierVertex verts[kVertsPerQuad], const float weight) {
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000653 SkScalar k[3];
654 SkScalar l[3];
655 SkScalar m[3];
656
657 calc_conic_klm(p, weight, k, l, m);
egdaniel@google.com5383a752013-07-12 20:15:34 +0000658
659 for (int i = 0; i < kVertsPerQuad; ++i) {
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000660 const SkPoint pnt = verts[i].fPos;
661 verts[i].fConic.fK = pnt.fX * k[0] + pnt.fY * k[1] + k[2];
662 verts[i].fConic.fL = pnt.fX * l[0] + pnt.fY * l[1] + l[2];
663 verts[i].fConic.fM = pnt.fX * m[0] + pnt.fY * m[1] + m[2];
egdaniel@google.com5383a752013-07-12 20:15:34 +0000664 }
665}
666
667void add_conics(const SkPoint p[3],
668 float weight,
669 const SkMatrix* toDevice,
670 const SkMatrix* toSrc,
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000671 BezierVertex** vert,
egdaniel@google.com5383a752013-07-12 20:15:34 +0000672 SkRect* devBounds) {
673 bloat_quad(p, toDevice, toSrc, *vert, devBounds);
674 set_conic_coeffs(p, *vert, weight);
675 *vert += kVertsPerQuad;
676}
677
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000678void add_quads(const SkPoint p[3],
679 int subdiv,
bsalomon@google.comb9086a02012-11-01 18:02:54 +0000680 const SkMatrix* toDevice,
681 const SkMatrix* toSrc,
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000682 BezierVertex** vert,
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +0000683 SkRect* devBounds) {
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000684 SkASSERT(subdiv >= 0);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000685 if (subdiv) {
686 SkPoint newP[5];
687 SkChopQuadAtHalf(p, newP);
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +0000688 add_quads(newP + 0, subdiv-1, toDevice, toSrc, vert, devBounds);
689 add_quads(newP + 2, subdiv-1, toDevice, toSrc, vert, devBounds);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000690 } else {
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +0000691 bloat_quad(p, toDevice, toSrc, *vert, devBounds);
egdaniel@google.com34b05ca2013-08-05 20:43:12 +0000692 set_uv_quad(p, *vert);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000693 *vert += kVertsPerQuad;
694 }
695}
696
697void add_line(const SkPoint p[2],
698 int rtHeight,
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000699 const SkMatrix* toSrc,
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000700 GrColor coverage,
701 LineVertex** vert) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000702 const SkPoint& a = p[0];
703 const SkPoint& b = p[1];
704
705 SkVector orthVec = b;
706 orthVec -= a;
707
708 if (orthVec.setLength(SK_Scalar1)) {
709 orthVec.setOrthog(orthVec);
710
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000711 for (int i = 0; i < kVertsPerLineSeg; ++i) {
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000712 (*vert)[i].fPos = (i & 0x1) ? b : a;
713 if (i & 0x2) {
jvanverth@google.com5e2d2702013-08-14 14:29:29 +0000714 (*vert)[i].fPos += orthVec;
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000715 (*vert)[i].fCoverage = 0;
716 } else if (i & 0x4) {
717 (*vert)[i].fPos -= orthVec;
718 (*vert)[i].fCoverage = 0;
719 } else {
720 (*vert)[i].fCoverage = coverage;
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000721 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000722 }
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000723 if (NULL != toSrc) {
724 toSrc->mapPointsWithStride(&(*vert)->fPos,
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000725 sizeof(LineVertex),
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000726 kVertsPerLineSeg);
727 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000728 } else {
729 // just make it degenerate and likely offscreen
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000730 for (int i = 0; i < kVertsPerLineSeg; ++i) {
731 (*vert)[i].fPos.set(SK_ScalarMax, SK_ScalarMax);
732 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000733 }
734
735 *vert += kVertsPerLineSeg;
736}
737
738}
739
egdaniel@google.com5383a752013-07-12 20:15:34 +0000740/**
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000741 * Shader is based off of Loop-Blinn Quadratic GPU Rendering
egdaniel@google.com5383a752013-07-12 20:15:34 +0000742 * The output of this effect is a hairline edge for conics.
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000743 * Conics specified by implicit equation K^2 - LM.
744 * K, L, and M, are the first three values of the vertex attribute,
745 * the fourth value is not used. Distance is calculated using a
746 * first order approximation from the taylor series.
egdaniel@google.com5383a752013-07-12 20:15:34 +0000747 * Coverage is max(0, 1-distance).
748 */
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000749
750/**
751 * Test were also run using a second order distance approximation.
752 * There were two versions of the second order approx. The first version
753 * is of roughly the form:
754 * f(q) = |f(p)| - ||f'(p)||*||q-p|| - ||f''(p)||*||q-p||^2.
755 * The second is similar:
756 * f(q) = |f(p)| + ||f'(p)||*||q-p|| + ||f''(p)||*||q-p||^2.
757 * The exact version of the equations can be found in the paper
758 * "Distance Approximations for Rasterizing Implicit Curves" by Gabriel Taubin
759 *
760 * In both versions we solve the quadratic for ||q-p||.
761 * Version 1:
762 * gFM is magnitude of first partials and gFM2 is magnitude of 2nd partials (as derived from paper)
763 * builder->fsCodeAppend("\t\tedgeAlpha = (sqrt(gFM*gFM+4.0*func*gF2M) - gFM)/(2.0*gF2M);\n");
764 * Version 2:
765 * builder->fsCodeAppend("\t\tedgeAlpha = (gFM - sqrt(gFM*gFM-4.0*func*gF2M))/(2.0*gF2M);\n");
766 *
767 * Also note that 2nd partials of k,l,m are zero
768 *
769 * When comparing the two second order approximations to the first order approximations,
770 * the following results were found. Version 1 tends to underestimate the distances, thus it
771 * basically increases all the error that we were already seeing in the first order
772 * approx. So this version is not the one to use. Version 2 has the opposite effect
773 * and tends to overestimate the distances. This is much closer to what we are
774 * looking for. It is able to render ellipses (even thin ones) without the need to chop.
775 * However, it can not handle thin hyperbolas well and thus would still rely on
776 * chopping to tighten the clipping. Another side effect of the overestimating is
777 * that the curves become much thinner and "ropey". If all that was ever rendered
778 * were "not too thin" curves and ellipses then 2nd order may have an advantage since
779 * only one geometry would need to be rendered. However no benches were run comparing
780 * chopped first order and non chopped 2nd order.
781 */
egdaniel@google.com5383a752013-07-12 20:15:34 +0000782class HairConicEdgeEffect : public GrEffect {
783public:
784 static GrEffectRef* Create() {
785 GR_CREATE_STATIC_EFFECT(gHairConicEdgeEffect, HairConicEdgeEffect, ());
786 gHairConicEdgeEffect->ref();
787 return gHairConicEdgeEffect;
788 }
789
790 virtual ~HairConicEdgeEffect() {}
791
792 static const char* Name() { return "HairConicEdge"; }
793
794 virtual void getConstantColorComponents(GrColor* color,
795 uint32_t* validFlags) const SK_OVERRIDE {
796 *validFlags = 0;
797 }
798
799 virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
800 return GrTBackendEffectFactory<HairConicEdgeEffect>::getInstance();
801 }
802
803 class GLEffect : public GrGLEffect {
804 public:
805 GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
806 : INHERITED (factory) {}
807
808 virtual void emitCode(GrGLShaderBuilder* builder,
809 const GrDrawEffect& drawEffect,
810 EffectKey key,
811 const char* outputColor,
812 const char* inputColor,
813 const TextureSamplerArray& samplers) SK_OVERRIDE {
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000814 const char *vsName, *fsName;
egdaniel@google.com5383a752013-07-12 20:15:34 +0000815
816 SkAssertResult(builder->enableFeature(
817 GrGLShaderBuilder::kStandardDerivatives_GLSLFeature));
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000818 builder->addVarying(kVec4f_GrSLType, "ConicCoeffs",
819 &vsName, &fsName);
egdaniel@google.com5383a752013-07-12 20:15:34 +0000820 const SkString* attr0Name =
821 builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]);
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000822 builder->vsCodeAppendf("\t%s = %s;\n", vsName, attr0Name->c_str());
egdaniel@google.com5383a752013-07-12 20:15:34 +0000823
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000824 builder->fsCodeAppend("\t\tfloat edgeAlpha;\n");
egdaniel@google.com5383a752013-07-12 20:15:34 +0000825
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000826 builder->fsCodeAppendf("\t\tvec3 dklmdx = dFdx(%s.xyz);\n", fsName);
827 builder->fsCodeAppendf("\t\tvec3 dklmdy = dFdy(%s.xyz);\n", fsName);
828 builder->fsCodeAppendf("\t\tfloat dfdx =\n"
829 "\t\t\t2.0*%s.x*dklmdx.x - %s.y*dklmdx.z - %s.z*dklmdx.y;\n",
830 fsName, fsName, fsName);
831 builder->fsCodeAppendf("\t\tfloat dfdy =\n"
832 "\t\t\t2.0*%s.x*dklmdy.x - %s.y*dklmdy.z - %s.z*dklmdy.y;\n",
833 fsName, fsName, fsName);
834 builder->fsCodeAppend("\t\tvec2 gF = vec2(dfdx, dfdy);\n");
835 builder->fsCodeAppend("\t\tfloat gFM = sqrt(dot(gF, gF));\n");
836 builder->fsCodeAppendf("\t\tfloat func = abs(%s.x*%s.x - %s.y*%s.z);\n", fsName, fsName,
837 fsName, fsName);
838 builder->fsCodeAppend("\t\tedgeAlpha = func / gFM;\n");
839 builder->fsCodeAppend("\t\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
egdaniel@google.com5383a752013-07-12 20:15:34 +0000840 // Add line below for smooth cubic ramp
841 // builder->fsCodeAppend("\t\tedgeAlpha = edgeAlpha*edgeAlpha*(3.0-2.0*edgeAlpha);\n");
842
843 SkString modulate;
844 GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha");
845 builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
846 }
847
848 static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
849 return 0x0;
850 }
851
852 virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE {}
853
854 private:
855 typedef GrGLEffect INHERITED;
856 };
857
858private:
859 HairConicEdgeEffect() {
860 this->addVertexAttrib(kVec4f_GrSLType);
egdaniel@google.com5383a752013-07-12 20:15:34 +0000861 }
862
863 virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE {
864 return true;
865 }
866
867 GR_DECLARE_EFFECT_TEST;
868
869 typedef GrEffect INHERITED;
870};
871
872GR_DEFINE_EFFECT_TEST(HairConicEdgeEffect);
873
874GrEffectRef* HairConicEdgeEffect::TestCreate(SkMWCRandom* random,
875 GrContext*,
876 const GrDrawTargetCaps& caps,
877 GrTexture*[]) {
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000878 return caps.shaderDerivativeSupport() ? HairConicEdgeEffect::Create() : NULL;
egdaniel@google.com5383a752013-07-12 20:15:34 +0000879}
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000880
881/**
882 * The output of this effect is a hairline edge for quadratics.
883 * Quadratic specified by 0=u^2-v canonical coords. u and v are the first
884 * two components of the vertex attribute. Uses unsigned distance.
skia.committer@gmail.com041e2db2013-04-03 07:01:14 +0000885 * Coverage is min(0, 1-distance). 3rd & 4th component unused.
886 * Requires shader derivative instruction support.
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000887 */
888class HairQuadEdgeEffect : public GrEffect {
889public:
890
891 static GrEffectRef* Create() {
bsalomon@google.comd42aca32013-04-23 15:37:27 +0000892 GR_CREATE_STATIC_EFFECT(gHairQuadEdgeEffect, HairQuadEdgeEffect, ());
893 gHairQuadEdgeEffect->ref();
894 return gHairQuadEdgeEffect;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000895 }
896
897 virtual ~HairQuadEdgeEffect() {}
898
899 static const char* Name() { return "HairQuadEdge"; }
900
skia.committer@gmail.com041e2db2013-04-03 07:01:14 +0000901 virtual void getConstantColorComponents(GrColor* color,
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000902 uint32_t* validFlags) const SK_OVERRIDE {
903 *validFlags = 0;
904 }
905
906 virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
907 return GrTBackendEffectFactory<HairQuadEdgeEffect>::getInstance();
908 }
909
910 class GLEffect : public GrGLEffect {
911 public:
912 GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
913 : INHERITED (factory) {}
914
915 virtual void emitCode(GrGLShaderBuilder* builder,
916 const GrDrawEffect& drawEffect,
917 EffectKey key,
918 const char* outputColor,
919 const char* inputColor,
920 const TextureSamplerArray& samplers) SK_OVERRIDE {
921 const char *vsName, *fsName;
922 const SkString* attrName =
923 builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]);
924 builder->fsCodeAppendf("\t\tfloat edgeAlpha;\n");
925
926 SkAssertResult(builder->enableFeature(
egdaniel@google.com5383a752013-07-12 20:15:34 +0000927 GrGLShaderBuilder::kStandardDerivatives_GLSLFeature));
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000928 builder->addVarying(kVec4f_GrSLType, "HairQuadEdge", &vsName, &fsName);
929
930 builder->fsCodeAppendf("\t\tvec2 duvdx = dFdx(%s.xy);\n", fsName);
931 builder->fsCodeAppendf("\t\tvec2 duvdy = dFdy(%s.xy);\n", fsName);
932 builder->fsCodeAppendf("\t\tvec2 gF = vec2(2.0*%s.x*duvdx.x - duvdx.y,\n"
933 "\t\t 2.0*%s.x*duvdy.x - duvdy.y);\n",
934 fsName, fsName);
935 builder->fsCodeAppendf("\t\tedgeAlpha = (%s.x*%s.x - %s.y);\n", fsName, fsName,
936 fsName);
937 builder->fsCodeAppend("\t\tedgeAlpha = sqrt(edgeAlpha*edgeAlpha / dot(gF, gF));\n");
938 builder->fsCodeAppend("\t\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
939
940 SkString modulate;
bsalomon@google.com018f1792013-04-18 19:36:09 +0000941 GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha");
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000942 builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
943
944 builder->vsCodeAppendf("\t%s = %s;\n", vsName, attrName->c_str());
945 }
946
947 static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
948 return 0x0;
949 }
950
951 virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE {}
952
953 private:
954 typedef GrGLEffect INHERITED;
955 };
956
957private:
skia.committer@gmail.com041e2db2013-04-03 07:01:14 +0000958 HairQuadEdgeEffect() {
959 this->addVertexAttrib(kVec4f_GrSLType);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000960 }
961
962 virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE {
963 return true;
964 }
965
966 GR_DECLARE_EFFECT_TEST;
967
968 typedef GrEffect INHERITED;
969};
970
971GR_DEFINE_EFFECT_TEST(HairQuadEdgeEffect);
972
973GrEffectRef* HairQuadEdgeEffect::TestCreate(SkMWCRandom* random,
974 GrContext*,
975 const GrDrawTargetCaps& caps,
976 GrTexture*[]) {
977 // Doesn't work without derivative instructions.
egdaniel@google.com5383a752013-07-12 20:15:34 +0000978 return caps.shaderDerivativeSupport() ? HairQuadEdgeEffect::Create() : NULL;
979}
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000980
981///////////////////////////////////////////////////////////////////////////////
982
robertphillips@google.com42903302013-04-20 12:26:07 +0000983namespace {
984
985// position + edge
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000986extern const GrVertexAttrib gHairlineBezierAttribs[] = {
robertphillips@google.com42903302013-04-20 12:26:07 +0000987 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
988 {kVec4f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding}
989};
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000990
991// position + coverage
992extern const GrVertexAttrib gHairlineLineAttribs[] = {
993 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
994 {kVec4ub_GrVertexAttribType, sizeof(GrPoint), kCoverage_GrVertexAttribBinding},
robertphillips@google.com42903302013-04-20 12:26:07 +0000995};
996
jvanverth@google.com681ccf02013-08-16 14:51:51 +0000997};
998
999bool GrAAHairLinePathRenderer::createLineGeom(
bsalomon@google.comb3729422012-03-07 19:13:28 +00001000 const SkPath& path,
bsalomon@google.comb3729422012-03-07 19:13:28 +00001001 GrDrawTarget* target,
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001002 const PtArray& lines,
1003 int lineCnt,
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +00001004 GrDrawTarget::AutoReleaseGeometry* arg,
1005 SkRect* devBounds) {
jvanverth@google.com9b855c72013-03-01 18:21:22 +00001006 GrDrawState* drawState = target->drawState();
1007 int rtHeight = drawState->getRenderTarget()->height();
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001008
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001009 const SkMatrix& viewM = drawState->getViewMatrix();
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001010
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001011 *devBounds = path.getBounds();
1012 viewM.mapRect(devBounds);
1013 devBounds->outset(SK_Scalar1, SK_Scalar1);
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001014
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001015 int vertCnt = kVertsPerLineSeg * lineCnt;
1016
1017 target->drawState()->setVertexAttribs<gHairlineLineAttribs>(SK_ARRAY_COUNT(gHairlineLineAttribs));
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +00001018 SkASSERT(sizeof(LineVertex) == target->getDrawState().getVertexSize());
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001019
1020 if (!arg->set(target, vertCnt, 0)) {
1021 return false;
1022 }
1023
1024 LineVertex* verts = reinterpret_cast<LineVertex*>(arg->vertices());
1025
1026 const SkMatrix* toSrc = NULL;
1027 SkMatrix ivm;
1028
1029 if (viewM.hasPerspective()) {
1030 if (viewM.invert(&ivm)) {
1031 toSrc = &ivm;
1032 }
1033 }
1034
1035 for (int i = 0; i < lineCnt; ++i) {
1036 add_line(&lines[2*i], rtHeight, toSrc, drawState->getCoverage(), &verts);
1037 }
1038
1039 return true;
1040}
1041
1042bool GrAAHairLinePathRenderer::createBezierGeom(
1043 const SkPath& path,
1044 GrDrawTarget* target,
1045 const PtArray& quads,
1046 int quadCnt,
1047 const PtArray& conics,
1048 int conicCnt,
1049 const IntArray& qSubdivs,
1050 const FloatArray& cWeights,
1051 GrDrawTarget::AutoReleaseGeometry* arg,
1052 SkRect* devBounds) {
1053 GrDrawState* drawState = target->drawState();
1054
1055 const SkMatrix& viewM = drawState->getViewMatrix();
1056
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +00001057 // All the vertices that we compute are within 1 of path control points with the exception of
1058 // one of the bounding vertices for each quad. The add_quads() function will update the bounds
1059 // for each quad added.
1060 *devBounds = path.getBounds();
1061 viewM.mapRect(devBounds);
1062 devBounds->outset(SK_Scalar1, SK_Scalar1);
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001063
1064 int vertCnt = kVertsPerQuad * quadCnt + kVertsPerQuad * conicCnt;
1065
1066 target->drawState()->setVertexAttribs<gHairlineBezierAttribs>(SK_ARRAY_COUNT(gHairlineBezierAttribs));
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +00001067 SkASSERT(sizeof(BezierVertex) == target->getDrawState().getVertexSize());
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001068
jvanverth@google.comb75b0a02013-02-05 20:33:30 +00001069 if (!arg->set(target, vertCnt, 0)) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001070 return false;
1071 }
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001072
1073 BezierVertex* verts = reinterpret_cast<BezierVertex*>(arg->vertices());
1074
bsalomon@google.comb9086a02012-11-01 18:02:54 +00001075 const SkMatrix* toDevice = NULL;
1076 const SkMatrix* toSrc = NULL;
1077 SkMatrix ivm;
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001078
bsalomon@google.comdbeeac32011-09-12 14:59:34 +00001079 if (viewM.hasPerspective()) {
1080 if (viewM.invert(&ivm)) {
1081 toDevice = &viewM;
1082 toSrc = &ivm;
1083 }
1084 }
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001085
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001086 int unsubdivQuadCnt = quads.count() / 3;
1087 for (int i = 0; i < unsubdivQuadCnt; ++i) {
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +00001088 SkASSERT(qSubdivs[i] >= 0);
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +00001089 add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts, devBounds);
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001090 }
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001091
egdaniel@google.com5383a752013-07-12 20:15:34 +00001092 // Start Conics
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001093 for (int i = 0; i < conicCnt; ++i) {
egdaniel@google.com5383a752013-07-12 20:15:34 +00001094 add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &verts, devBounds);
1095 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001096 return true;
1097}
1098
robertphillips@google.com8a4fc402012-05-24 12:42:24 +00001099bool GrAAHairLinePathRenderer::canDrawPath(const SkPath& path,
sugoi@google.com5f74cf82012-12-17 21:16:45 +00001100 const SkStrokeRec& stroke,
robertphillips@google.com8a4fc402012-05-24 12:42:24 +00001101 const GrDrawTarget* target,
1102 bool antiAlias) const {
sugoi@google.com5f74cf82012-12-17 21:16:45 +00001103 if (!stroke.isHairlineStyle() || !antiAlias) {
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001104 return false;
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001105 }
1106
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +00001107 if (SkPath::kLine_SegmentMask == path.getSegmentMasks() ||
1108 target->caps()->shaderDerivativeSupport()) {
1109 return true;
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001110 }
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +00001111 return false;
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001112}
1113
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001114template <class VertexType>
1115bool check_bounds(GrDrawState* drawState, const SkRect& devBounds, void* vertices, int vCount)
1116{
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +00001117 SkRect tolDevBounds = devBounds;
1118 tolDevBounds.outset(SK_Scalar1 / 10000, SK_Scalar1 / 10000);
1119 SkRect actualBounds;
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001120
1121 VertexType* verts = reinterpret_cast<VertexType*>(vertices);
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +00001122 bool first = true;
1123 for (int i = 0; i < vCount; ++i) {
1124 SkPoint pos = verts[i].fPos;
1125 // This is a hack to workaround the fact that we move some degenerate segments offscreen.
1126 if (SK_ScalarMax == pos.fX) {
1127 continue;
1128 }
1129 drawState->getViewMatrix().mapPoints(&pos, 1);
1130 if (first) {
1131 actualBounds.set(pos.fX, pos.fY, pos.fX, pos.fY);
1132 first = false;
1133 } else {
1134 actualBounds.growToInclude(pos.fX, pos.fY);
1135 }
1136 }
1137 if (!first) {
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001138 return tolDevBounds.contains(actualBounds);
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +00001139 }
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001140
1141 return true;
1142}
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +00001143
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001144bool GrAAHairLinePathRenderer::onDrawPath(const SkPath& path,
1145 const SkStrokeRec&,
1146 GrDrawTarget* target,
1147 bool antiAlias) {
1148
1149 GrDrawState* drawState = target->drawState();
1150
1151 SkIRect devClipBounds;
1152 target->getClip()->getConservativeBounds(drawState->getRenderTarget(), &devClipBounds);
1153
1154 int lineCnt;
1155 int quadCnt;
1156 int conicCnt;
1157 PREALLOC_PTARRAY(128) lines;
1158 PREALLOC_PTARRAY(128) quads;
1159 PREALLOC_PTARRAY(128) conics;
1160 IntArray qSubdivs;
1161 FloatArray cWeights;
1162 quadCnt = generate_lines_and_quads(path, drawState->getViewMatrix(), devClipBounds,
1163 &lines, &quads, &conics, &qSubdivs, &cWeights);
1164 lineCnt = lines.count() / 2;
1165 conicCnt = conics.count() / 3;
1166
1167 // do lines first
bsalomon@google.comeb6879f2013-06-13 19:34:18 +00001168 {
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001169 GrDrawTarget::AutoReleaseGeometry arg;
1170 SkRect devBounds;
1171
1172 if (!this->createLineGeom(path,
1173 target,
1174 lines,
1175 lineCnt,
1176 &arg,
1177 &devBounds)) {
1178 return false;
1179 }
1180
1181 GrDrawTarget::AutoStateRestore asr;
1182
1183 // createGeom transforms the geometry to device space when the matrix does not have
1184 // perspective.
1185 if (target->getDrawState().getViewMatrix().hasPerspective()) {
1186 asr.set(target, GrDrawTarget::kPreserve_ASRInit);
1187 } else if (!asr.setIdentity(target, GrDrawTarget::kPreserve_ASRInit)) {
1188 return false;
1189 }
1190 GrDrawState* drawState = target->drawState();
1191
1192 // Check devBounds
1193 SkASSERT(check_bounds<LineVertex>(drawState, devBounds, arg.vertices(),
1194 kVertsPerLineSeg * lineCnt));
1195
1196 {
1197 GrDrawState::AutoRestoreEffects are(drawState);
1198 target->setIndexSourceToBuffer(fLinesIndexBuffer);
1199 int lines = 0;
1200 while (lines < lineCnt) {
1201 int n = GrMin(lineCnt - lines, kNumLineSegsInIdxBuffer);
1202 target->drawIndexed(kTriangles_GrPrimitiveType,
1203 kVertsPerLineSeg*lines, // startV
1204 0, // startI
1205 kVertsPerLineSeg*n, // vCount
1206 kIdxsPerLineSeg*n,
1207 &devBounds); // iCount
1208 lines += n;
1209 }
bsalomon@google.comeb6879f2013-06-13 19:34:18 +00001210 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001211 }
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001212
1213 // then quadratics/conics
egdaniel@google.com5383a752013-07-12 20:15:34 +00001214 {
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001215 GrDrawTarget::AutoReleaseGeometry arg;
1216 SkRect devBounds;
1217
1218 if (!this->createBezierGeom(path,
1219 target,
1220 quads,
1221 quadCnt,
1222 conics,
1223 conicCnt,
1224 qSubdivs,
1225 cWeights,
1226 &arg,
1227 &devBounds)) {
1228 return false;
1229 }
1230
1231 GrDrawTarget::AutoStateRestore asr;
1232
1233 // createGeom transforms the geometry to device space when the matrix does not have
1234 // perspective.
1235 if (target->getDrawState().getViewMatrix().hasPerspective()) {
1236 asr.set(target, GrDrawTarget::kPreserve_ASRInit);
1237 } else if (!asr.setIdentity(target, GrDrawTarget::kPreserve_ASRInit)) {
1238 return false;
1239 }
1240 GrDrawState* drawState = target->drawState();
1241
1242 static const int kEdgeAttrIndex = 1;
1243
1244 GrEffectRef* hairQuadEffect = HairQuadEdgeEffect::Create();
1245 GrEffectRef* hairConicEffect = HairConicEdgeEffect::Create();
1246
1247 // Check devBounds
1248 SkASSERT(check_bounds<BezierVertex>(drawState, devBounds, arg.vertices(),
1249 kVertsPerQuad * quadCnt + kVertsPerQuad * conicCnt));
1250
1251 {
1252 GrDrawState::AutoRestoreEffects are(drawState);
1253 target->setIndexSourceToBuffer(fQuadsIndexBuffer);
1254 int quads = 0;
1255 drawState->addCoverageEffect(hairQuadEffect, kEdgeAttrIndex)->unref();
1256 while (quads < quadCnt) {
1257 int n = GrMin(quadCnt - quads, kNumQuadsInIdxBuffer);
1258 target->drawIndexed(kTriangles_GrPrimitiveType,
1259 kVertsPerQuad*quads, // startV
1260 0, // startI
1261 kVertsPerQuad*n, // vCount
1262 kIdxsPerQuad*n, // iCount
1263 &devBounds);
1264 quads += n;
1265 }
1266 }
1267
1268 {
1269 GrDrawState::AutoRestoreEffects are(drawState);
1270 int conics = 0;
1271 drawState->addCoverageEffect(hairConicEffect, 1, 2)->unref();
1272 while (conics < conicCnt) {
1273 int n = GrMin(conicCnt - conics, kNumQuadsInIdxBuffer);
1274 target->drawIndexed(kTriangles_GrPrimitiveType,
1275 kVertsPerQuad*(quadCnt + conics), // startV
1276 0, // startI
1277 kVertsPerQuad*n, // vCount
1278 kIdxsPerQuad*n, // iCount
1279 &devBounds);
1280 conics += n;
1281 }
egdaniel@google.com5383a752013-07-12 20:15:34 +00001282 }
1283 }
jvanverth@google.com681ccf02013-08-16 14:51:51 +00001284
bsalomon@google.com0406b9e2013-04-02 21:00:15 +00001285 target->resetIndexSource();
bsalomon@google.com4647f902013-03-26 14:45:27 +00001286
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001287 return true;
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001288}