blob: c1f951ab7f91832d95ece209f71b377794c68e48 [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
32static const int kVertsPerLineSeg = 4;
33static const int kIdxsPerLineSeg = 6;
34
35static const int kNumQuadsInIdxBuffer = 256;
36static const size_t kQuadIdxSBufize = kIdxsPerQuad *
37 sizeof(uint16_t) *
38 kNumQuadsInIdxBuffer;
39
40bool push_quad_index_data(GrIndexBuffer* qIdxBuffer) {
41 uint16_t* data = (uint16_t*) qIdxBuffer->lock();
42 bool tempData = NULL == data;
43 if (tempData) {
tomhudson@google.comc377baf2012-07-09 20:17:56 +000044 data = SkNEW_ARRAY(uint16_t, kNumQuadsInIdxBuffer * kIdxsPerQuad);
bsalomon@google.comaeb21602011-08-30 18:13:44 +000045 }
46 for (int i = 0; i < kNumQuadsInIdxBuffer; ++i) {
47
48 // Each quadratic is rendered as a five sided polygon. This poly bounds
49 // the quadratic's bounding triangle but has been expanded so that the
50 // 1-pixel wide area around the curve is inside the poly.
51 // If a,b,c are the original control points then the poly a0,b0,c0,c1,a1
52 // that is rendered would look like this:
53 // b0
54 // b
55 //
56 // a0 c0
57 // a c
58 // a1 c1
bsalomon@google.com0e5104c2012-04-10 16:20:41 +000059 // Each is drawn as three triangles specified by these 9 indices:
bsalomon@google.comaeb21602011-08-30 18:13:44 +000060 int baseIdx = i * kIdxsPerQuad;
61 uint16_t baseVert = (uint16_t)(i * kVertsPerQuad);
62 data[0 + baseIdx] = baseVert + 0; // a0
63 data[1 + baseIdx] = baseVert + 1; // a1
64 data[2 + baseIdx] = baseVert + 2; // b0
65 data[3 + baseIdx] = baseVert + 2; // b0
66 data[4 + baseIdx] = baseVert + 4; // c1
67 data[5 + baseIdx] = baseVert + 3; // c0
68 data[6 + baseIdx] = baseVert + 1; // a1
69 data[7 + baseIdx] = baseVert + 4; // c1
70 data[8 + baseIdx] = baseVert + 2; // b0
71 }
72 if (tempData) {
73 bool ret = qIdxBuffer->updateData(data, kQuadIdxSBufize);
74 delete[] data;
75 return ret;
76 } else {
77 qIdxBuffer->unlock();
78 return true;
79 }
80}
81}
82
83GrPathRenderer* GrAAHairLinePathRenderer::Create(GrContext* context) {
bsalomon@google.coma8a6a322011-09-23 14:19:58 +000084 const GrIndexBuffer* lIdxBuffer = context->getQuadIndexBuffer();
85 if (NULL == lIdxBuffer) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +000086 return NULL;
87 }
bsalomon@google.coma8a6a322011-09-23 14:19:58 +000088 GrGpu* gpu = context->getGpu();
89 GrIndexBuffer* qIdxBuf = gpu->createIndexBuffer(kQuadIdxSBufize, false);
90 SkAutoTUnref<GrIndexBuffer> qIdxBuffer(qIdxBuf);
91 if (NULL == qIdxBuf ||
92 !push_quad_index_data(qIdxBuf)) {
93 return NULL;
94 }
tomhudson@google.comc377baf2012-07-09 20:17:56 +000095 return SkNEW_ARGS(GrAAHairLinePathRenderer,
96 (context, lIdxBuffer, qIdxBuf));
bsalomon@google.comaeb21602011-08-30 18:13:44 +000097}
98
99GrAAHairLinePathRenderer::GrAAHairLinePathRenderer(
100 const GrContext* context,
101 const GrIndexBuffer* linesIndexBuffer,
102 const GrIndexBuffer* quadsIndexBuffer) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000103 fLinesIndexBuffer = linesIndexBuffer;
104 linesIndexBuffer->ref();
105 fQuadsIndexBuffer = quadsIndexBuffer;
106 quadsIndexBuffer->ref();
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000107}
108
109GrAAHairLinePathRenderer::~GrAAHairLinePathRenderer() {
110 fLinesIndexBuffer->unref();
111 fQuadsIndexBuffer->unref();
112}
113
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000114namespace {
115
bsalomon@google.com49313f62011-09-14 13:54:05 +0000116typedef SkTArray<SkPoint, true> PtArray;
bsalomon@google.com92669012011-09-27 19:10:05 +0000117#define PREALLOC_PTARRAY(N) SkSTArray<(N),SkPoint, true>
bsalomon@google.com49313f62011-09-14 13:54:05 +0000118typedef SkTArray<int, true> IntArray;
egdaniel@google.com5383a752013-07-12 20:15:34 +0000119typedef SkTArray<float, true> FloatArray;
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000120
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000121// Takes 178th time of logf on Z600 / VC2010
122int get_float_exp(float x) {
123 GR_STATIC_ASSERT(sizeof(int) == sizeof(float));
124#if GR_DEBUG
125 static bool tested;
126 if (!tested) {
127 tested = true;
128 GrAssert(get_float_exp(0.25f) == -2);
129 GrAssert(get_float_exp(0.3f) == -2);
130 GrAssert(get_float_exp(0.5f) == -1);
131 GrAssert(get_float_exp(1.f) == 0);
132 GrAssert(get_float_exp(2.f) == 1);
133 GrAssert(get_float_exp(2.5f) == 1);
134 GrAssert(get_float_exp(8.f) == 3);
135 GrAssert(get_float_exp(100.f) == 6);
136 GrAssert(get_float_exp(1000.f) == 9);
137 GrAssert(get_float_exp(1024.f) == 10);
138 GrAssert(get_float_exp(3000000.f) == 21);
139 }
140#endif
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000141 const int* iptr = (const int*)&x;
142 return (((*iptr) & 0x7f800000) >> 23) - 127;
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000143}
144
egdaniel@google.com5383a752013-07-12 20:15:34 +0000145// Uses the max curvature function for quads to estimate
146// where to chop the conic. If the max curvature is not
147// found along the curve segment it will return 1 and
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000148// dst[0] is the original conic. If it returns 2 the dst[0]
egdaniel@google.com5383a752013-07-12 20:15:34 +0000149// and dst[1] are the two new conics.
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000150int split_conic(const SkPoint src[3], SkConic dst[2], const SkScalar weight) {
egdaniel@google.com5383a752013-07-12 20:15:34 +0000151 SkScalar t = SkFindQuadMaxCurvature(src);
152 if (t == 0) {
153 if (dst) {
154 dst[0].set(src, weight);
155 }
156 return 1;
157 } else {
158 if (dst) {
159 SkConic conic;
160 conic.set(src, weight);
161 conic.chopAt(t, dst);
162 }
163 return 2;
164 }
165}
166
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000167// Calls split_conic on the entire conic and then once more on each subsection.
168// Most cases will result in either 1 conic (chop point is not within t range)
169// or 3 points (split once and then one subsection is split again).
170int chop_conic(const SkPoint src[3], SkConic dst[4], const SkScalar weight) {
171 SkConic dstTemp[2];
172 int conicCnt = split_conic(src, dstTemp, weight);
173 if (2 == conicCnt) {
174 int conicCnt2 = split_conic(dstTemp[0].fPts, dst, dstTemp[0].fW);
175 conicCnt = conicCnt2 + split_conic(dstTemp[1].fPts, &dst[conicCnt2], dstTemp[1].fW);
176 } else {
177 dst[0] = dstTemp[0];
178 }
179 return conicCnt;
180}
181
egdaniel@google.com5383a752013-07-12 20:15:34 +0000182// returns 0 if quad/conic is degen or close to it
183// in this case approx the path with lines
184// otherwise returns 1
185int is_degen_quad_or_conic(const SkPoint p[3]) {
186 static const SkScalar gDegenerateToLineTol = SK_Scalar1;
187 static const SkScalar gDegenerateToLineTolSqd =
188 SkScalarMul(gDegenerateToLineTol, gDegenerateToLineTol);
189
190 if (p[0].distanceToSqd(p[1]) < gDegenerateToLineTolSqd ||
191 p[1].distanceToSqd(p[2]) < gDegenerateToLineTolSqd) {
192 return 1;
193 }
194
195 SkScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]);
196 if (dsqd < gDegenerateToLineTolSqd) {
197 return 1;
198 }
199
200 if (p[2].distanceToLineBetweenSqd(p[1], p[0]) < gDegenerateToLineTolSqd) {
201 return 1;
202 }
203 return 0;
204}
205
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000206// we subdivide the quads to avoid huge overfill
207// if it returns -1 then should be drawn as lines
208int num_quad_subdivs(const SkPoint p[3]) {
209 static const SkScalar gDegenerateToLineTol = SK_Scalar1;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000210 static const SkScalar gDegenerateToLineTolSqd =
bsalomon@google.com46a2a1e2011-09-06 22:10:52 +0000211 SkScalarMul(gDegenerateToLineTol, gDegenerateToLineTol);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000212
bsalomon@google.com46a2a1e2011-09-06 22:10:52 +0000213 if (p[0].distanceToSqd(p[1]) < gDegenerateToLineTolSqd ||
214 p[1].distanceToSqd(p[2]) < gDegenerateToLineTolSqd) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000215 return -1;
216 }
bsalomon@google.com46a2a1e2011-09-06 22:10:52 +0000217
bsalomon@google.com81712882012-11-01 17:12:34 +0000218 SkScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]);
bsalomon@google.com46a2a1e2011-09-06 22:10:52 +0000219 if (dsqd < gDegenerateToLineTolSqd) {
220 return -1;
221 }
222
223 if (p[2].distanceToLineBetweenSqd(p[1], p[0]) < gDegenerateToLineTolSqd) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000224 return -1;
225 }
226
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000227 // tolerance of triangle height in pixels
228 // tuned on windows Quadro FX 380 / Z600
229 // trade off of fill vs cpu time on verts
230 // maybe different when do this using gpu (geo or tess shaders)
231 static const SkScalar gSubdivTol = 175 * SK_Scalar1;
232
robertphillips@google.com7460b372012-04-25 16:54:51 +0000233 if (dsqd <= SkScalarMul(gSubdivTol, gSubdivTol)) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000234 return 0;
235 } else {
robertphillips@google.com87379e12013-03-29 12:11:10 +0000236 static const int kMaxSub = 4;
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000237 // subdividing the quad reduces d by 4. so we want x = log4(d/tol)
238 // = log4(d*d/tol*tol)/2
239 // = log2(d*d/tol*tol)
240
241#ifdef SK_SCALAR_IS_FLOAT
242 // +1 since we're ignoring the mantissa contribution.
243 int log = get_float_exp(dsqd/(gSubdivTol*gSubdivTol)) + 1;
244 log = GrMin(GrMax(0, log), kMaxSub);
245 return log;
246#else
robertphillips@google.com7460b372012-04-25 16:54:51 +0000247 SkScalar log = SkScalarLog(
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000248 SkScalarDiv(dsqd,
robertphillips@google.com7460b372012-04-25 16:54:51 +0000249 SkScalarMul(gSubdivTol, gSubdivTol)));
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000250 static const SkScalar conv = SkScalarInvert(SkScalarLog(2));
251 log = SkScalarMul(log, conv);
252 return GrMin(GrMax(0, SkScalarCeilToInt(log)),kMaxSub);
253#endif
254 }
255}
256
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000257/**
258 * Generates the lines and quads to be rendered. Lines are always recorded in
259 * device space. We will do a device space bloat to account for the 1pixel
260 * thickness.
261 * Quads are recorded in device space unless m contains
262 * perspective, then in they are in src space. We do this because we will
263 * subdivide large quads to reduce over-fill. This subdivision has to be
264 * performed before applying the perspective matrix.
265 */
266int generate_lines_and_quads(const SkPath& path,
267 const SkMatrix& m,
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000268 const SkIRect& devClipBounds,
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000269 PtArray* lines,
270 PtArray* quads,
egdaniel@google.com5383a752013-07-12 20:15:34 +0000271 PtArray* conics,
272 IntArray* quadSubdivCnts,
273 FloatArray* conicWeights) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000274 SkPath::Iter iter(path, false);
275
276 int totalQuadCount = 0;
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000277 SkRect bounds;
278 SkIRect ibounds;
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000279
280 bool persp = m.hasPerspective();
281
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000282 for (;;) {
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000283 GrPoint pathPts[4];
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000284 GrPoint devPts[4];
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000285 SkPath::Verb verb = iter.next(pathPts);
bsalomon@google.com94b284d2013-05-10 17:14:06 +0000286 switch (verb) {
egdaniel@google.com5383a752013-07-12 20:15:34 +0000287 case SkPath::kConic_Verb: {
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000288 SkConic dst[4];
289 // We chop the conics to create tighter clipping to hide error
290 // that appears near max curvature of very thin conics. Thin
291 // hyperbolas with high weight still show error.
egdaniel@google.com5383a752013-07-12 20:15:34 +0000292 int conicCnt = chop_conic(pathPts, dst, iter.conicWeight());
293 for (int i = 0; i < conicCnt; ++i) {
294 SkPoint* chopPnts = dst[i].fPts;
295 m.mapPoints(devPts, chopPnts, 3);
296 bounds.setBounds(devPts, 3);
297 bounds.outset(SK_Scalar1, SK_Scalar1);
298 bounds.roundOut(&ibounds);
299 if (SkIRect::Intersects(devClipBounds, ibounds)) {
300 if (is_degen_quad_or_conic(devPts)) {
301 SkPoint* pts = lines->push_back_n(4);
302 pts[0] = devPts[0];
303 pts[1] = devPts[1];
304 pts[2] = devPts[1];
305 pts[3] = devPts[2];
306 } else {
307 // when in perspective keep conics in src space
308 SkPoint* cPts = persp ? chopPnts : devPts;
309 SkPoint* pts = conics->push_back_n(3);
310 pts[0] = cPts[0];
311 pts[1] = cPts[1];
312 pts[2] = cPts[2];
313 conicWeights->push_back() = dst[i].fW;
314 }
315 }
316 }
reed@google.com277c3f82013-05-31 15:17:50 +0000317 break;
egdaniel@google.com5383a752013-07-12 20:15:34 +0000318 }
319 case SkPath::kMove_Verb:
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000320 break;
bsalomon@google.com94b284d2013-05-10 17:14:06 +0000321 case SkPath::kLine_Verb:
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000322 m.mapPoints(devPts, pathPts, 2);
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000323 bounds.setBounds(devPts, 2);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000324 bounds.outset(SK_Scalar1, SK_Scalar1);
325 bounds.roundOut(&ibounds);
robertphillips@google.com7b112892012-07-31 15:18:21 +0000326 if (SkIRect::Intersects(devClipBounds, ibounds)) {
bsalomon@google.coma996fec2011-09-13 18:49:13 +0000327 SkPoint* pts = lines->push_back_n(2);
328 pts[0] = devPts[0];
329 pts[1] = devPts[1];
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000330 }
331 break;
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000332 case SkPath::kQuad_Verb: {
333 SkPoint choppedPts[5];
334 // Chopping the quad helps when the quad is either degenerate or nearly degenerate.
335 // When it is degenerate it allows the approximation with lines to work since the
336 // chop point (if there is one) will be at the parabola's vertex. In the nearly
337 // degenerate the QuadUVMatrix computed for the points is almost singular which
338 // can cause rendering artifacts.
339 int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts);
340 for (int i = 0; i < n; ++i) {
341 SkPoint* quadPts = choppedPts + i * 2;
342 m.mapPoints(devPts, quadPts, 3);
343 bounds.setBounds(devPts, 3);
344 bounds.outset(SK_Scalar1, SK_Scalar1);
345 bounds.roundOut(&ibounds);
346
347 if (SkIRect::Intersects(devClipBounds, ibounds)) {
348 int subdiv = num_quad_subdivs(devPts);
349 GrAssert(subdiv >= -1);
350 if (-1 == subdiv) {
351 SkPoint* pts = lines->push_back_n(4);
352 pts[0] = devPts[0];
353 pts[1] = devPts[1];
354 pts[2] = devPts[1];
355 pts[3] = devPts[2];
356 } else {
357 // when in perspective keep quads in src space
358 SkPoint* qPts = persp ? quadPts : devPts;
359 SkPoint* pts = quads->push_back_n(3);
360 pts[0] = qPts[0];
361 pts[1] = qPts[1];
362 pts[2] = qPts[2];
363 quadSubdivCnts->push_back() = subdiv;
364 totalQuadCount += 1 << subdiv;
365 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000366 }
367 }
bsalomon@google.coma51ab842012-07-10 19:53:34 +0000368 break;
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000369 }
bsalomon@google.com94b284d2013-05-10 17:14:06 +0000370 case SkPath::kCubic_Verb:
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000371 m.mapPoints(devPts, pathPts, 4);
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000372 bounds.setBounds(devPts, 4);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000373 bounds.outset(SK_Scalar1, SK_Scalar1);
374 bounds.roundOut(&ibounds);
robertphillips@google.com7b112892012-07-31 15:18:21 +0000375 if (SkIRect::Intersects(devClipBounds, ibounds)) {
bsalomon@google.com92669012011-09-27 19:10:05 +0000376 PREALLOC_PTARRAY(32) q;
bsalomon@google.coma51ab842012-07-10 19:53:34 +0000377 // we don't need a direction if we aren't constraining the subdivision
378 static const SkPath::Direction kDummyDir = SkPath::kCCW_Direction;
bsalomon@google.com69cc6ad2012-01-17 14:25:10 +0000379 // We convert cubics to quadratics (for now).
380 // In perspective have to do conversion in src space.
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000381 if (persp) {
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000382 SkScalar tolScale =
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000383 GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m,
384 path.getBounds());
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +0000385 GrPathUtils::convertCubicToQuads(pathPts, tolScale, false, kDummyDir, &q);
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000386 } else {
bsalomon@google.coma51ab842012-07-10 19:53:34 +0000387 GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, false, kDummyDir, &q);
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000388 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000389 for (int i = 0; i < q.count(); i += 3) {
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000390 SkPoint* qInDevSpace;
391 // bounds has to be calculated in device space, but q is
392 // in src space when there is perspective.
393 if (persp) {
394 m.mapPoints(devPts, &q[i], 3);
395 bounds.setBounds(devPts, 3);
396 qInDevSpace = devPts;
397 } else {
398 bounds.setBounds(&q[i], 3);
399 qInDevSpace = &q[i];
400 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000401 bounds.outset(SK_Scalar1, SK_Scalar1);
402 bounds.roundOut(&ibounds);
robertphillips@google.com7b112892012-07-31 15:18:21 +0000403 if (SkIRect::Intersects(devClipBounds, ibounds)) {
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000404 int subdiv = num_quad_subdivs(qInDevSpace);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000405 GrAssert(subdiv >= -1);
406 if (-1 == subdiv) {
bsalomon@google.coma996fec2011-09-13 18:49:13 +0000407 SkPoint* pts = lines->push_back_n(4);
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000408 // lines should always be in device coords
bsalomon@google.coma996fec2011-09-13 18:49:13 +0000409 pts[0] = qInDevSpace[0];
410 pts[1] = qInDevSpace[1];
411 pts[2] = qInDevSpace[1];
412 pts[3] = qInDevSpace[2];
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000413 } else {
bsalomon@google.coma996fec2011-09-13 18:49:13 +0000414 SkPoint* pts = quads->push_back_n(3);
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000415 // q is already in src space when there is no
416 // perspective and dev coords otherwise.
bsalomon@google.coma996fec2011-09-13 18:49:13 +0000417 pts[0] = q[0 + i];
418 pts[1] = q[1 + i];
419 pts[2] = q[2 + i];
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000420 quadSubdivCnts->push_back() = subdiv;
421 totalQuadCount += 1 << subdiv;
422 }
423 }
424 }
425 }
bsalomon@google.coma51ab842012-07-10 19:53:34 +0000426 break;
bsalomon@google.com94b284d2013-05-10 17:14:06 +0000427 case SkPath::kClose_Verb:
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000428 break;
bsalomon@google.com94b284d2013-05-10 17:14:06 +0000429 case SkPath::kDone_Verb:
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000430 return totalQuadCount;
431 }
432 }
433}
434
435struct Vertex {
436 GrPoint fPos;
437 union {
438 struct {
bsalomon@google.com81712882012-11-01 17:12:34 +0000439 SkScalar fA;
440 SkScalar fB;
441 SkScalar fC;
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000442 } fLine;
egdaniel@google.com5383a752013-07-12 20:15:34 +0000443 struct {
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000444 SkScalar fK;
445 SkScalar fL;
446 SkScalar fM;
egdaniel@google.com5383a752013-07-12 20:15:34 +0000447 } fConic;
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000448 GrVec fQuadCoord;
449 struct {
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000450 SkScalar fBogus[4];
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000451 };
452 };
453};
egdaniel@google.com5383a752013-07-12 20:15:34 +0000454
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000455GR_STATIC_ASSERT(sizeof(Vertex) == 3 * sizeof(GrPoint));
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000456
457void intersect_lines(const SkPoint& ptA, const SkVector& normA,
458 const SkPoint& ptB, const SkVector& normB,
459 SkPoint* result) {
460
461 SkScalar lineAW = -normA.dot(ptA);
462 SkScalar lineBW = -normB.dot(ptB);
463
464 SkScalar wInv = SkScalarMul(normA.fX, normB.fY) -
egdaniel@google.com5383a752013-07-12 20:15:34 +0000465 SkScalarMul(normA.fY, normB.fX);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000466 wInv = SkScalarInvert(wInv);
467
468 result->fX = SkScalarMul(normA.fY, lineBW) - SkScalarMul(lineAW, normB.fY);
469 result->fX = SkScalarMul(result->fX, wInv);
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000470
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000471 result->fY = SkScalarMul(lineAW, normB.fX) - SkScalarMul(normA.fX, lineBW);
472 result->fY = SkScalarMul(result->fY, wInv);
473}
474
egdaniel@google.com34b05ca2013-08-05 20:43:12 +0000475void set_uv_quad(const SkPoint qpts[3], Vertex verts[kVertsPerQuad]) {
476 // this should be in the src space, not dev coords, when we have perspective
477 GrPathUtils::QuadUVMatrix DevToUV(qpts);
478 DevToUV.apply<kVertsPerQuad, sizeof(Vertex), sizeof(GrPoint)>(verts);
479}
480
bsalomon@google.comb9086a02012-11-01 18:02:54 +0000481void bloat_quad(const SkPoint qpts[3], const SkMatrix* toDevice,
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +0000482 const SkMatrix* toSrc, Vertex verts[kVertsPerQuad],
483 SkRect* devBounds) {
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000484 GrAssert(!toDevice == !toSrc);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000485 // original quad is specified by tri a,b,c
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000486 SkPoint a = qpts[0];
487 SkPoint b = qpts[1];
488 SkPoint c = qpts[2];
489
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000490 if (toDevice) {
491 toDevice->mapPoints(&a, 1);
492 toDevice->mapPoints(&b, 1);
493 toDevice->mapPoints(&c, 1);
494 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000495 // make a new poly where we replace a and c by a 1-pixel wide edges orthog
496 // to edges ab and bc:
497 //
498 // before | after
499 // | b0
500 // b |
501 // |
502 // | a0 c0
503 // a c | a1 c1
504 //
505 // edges a0->b0 and b0->c0 are parallel to original edges a->b and b->c,
506 // respectively.
507 Vertex& a0 = verts[0];
508 Vertex& a1 = verts[1];
509 Vertex& b0 = verts[2];
510 Vertex& c0 = verts[3];
511 Vertex& c1 = verts[4];
512
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000513 SkVector ab = b;
514 ab -= a;
515 SkVector ac = c;
516 ac -= a;
517 SkVector cb = b;
518 cb -= c;
519
520 // We should have already handled degenerates
521 GrAssert(ab.length() > 0 && cb.length() > 0);
522
523 ab.normalize();
524 SkVector abN;
525 abN.setOrthog(ab, SkVector::kLeft_Side);
526 if (abN.dot(ac) > 0) {
527 abN.negate();
528 }
529
530 cb.normalize();
531 SkVector cbN;
532 cbN.setOrthog(cb, SkVector::kLeft_Side);
533 if (cbN.dot(ac) < 0) {
534 cbN.negate();
535 }
536
537 a0.fPos = a;
538 a0.fPos += abN;
539 a1.fPos = a;
540 a1.fPos -= abN;
541
542 c0.fPos = c;
543 c0.fPos += cbN;
544 c1.fPos = c;
545 c1.fPos -= cbN;
546
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +0000547 // This point may not be within 1 pixel of a control point. We update the bounding box to
548 // include it.
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000549 intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos);
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +0000550 devBounds->growToInclude(b0.fPos.fX, b0.fPos.fY);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000551
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000552 if (toSrc) {
553 toSrc->mapPointsWithStride(&verts[0].fPos, sizeof(Vertex), kVertsPerQuad);
554 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000555}
556
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000557// Input:
558// Three control points: p[0], p[1], p[2] and weight: w
559// Output:
560// Let:
561// l = (2*w * (y1 - y0), 2*w * (x0 - x1), 2*w * (x1*y0 - x0*y1))
562// m = (2*w * (y2 - y1), 2*w * (x1 - x2), 2*w * (x2*y1 - x1*y2))
563// k = (y2 - y0, x0 - x2, (x2 - x0)*y0 - (y2 - y0)*x0 )
564void calc_conic_klm(const SkPoint p[3], const SkScalar weight,
565 SkScalar k[3], SkScalar l[3], SkScalar m[3]) {
566 const SkScalar w2 = 2 * weight;
567 l[0] = w2 * (p[1].fY - p[0].fY);
568 l[1] = w2 * (p[0].fX - p[1].fX);
569 l[2] = w2 * (p[1].fX * p[0].fY - p[0].fX * p[1].fY);
egdaniel@google.com5383a752013-07-12 20:15:34 +0000570
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000571 m[0] = w2 * (p[2].fY - p[1].fY);
572 m[1] = w2 * (p[1].fX - p[2].fX);
573 m[2] = w2 * (p[2].fX * p[1].fY - p[1].fX * p[2].fY);
574
575 k[0] = p[2].fY - p[0].fY;
576 k[1] = p[0].fX - p[2].fX;
577 k[2] = (p[2].fX - p[0].fX) * p[0].fY - (p[2].fY - p[0].fY) * p[0].fX;
578
579 // scale the max absolute value of coeffs to 10
580 SkScalar scale = 0.0f;
581 for (int i = 0; i < 3; ++i) {
582 scale = SkMaxScalar(scale, SkScalarAbs(k[i]));
583 scale = SkMaxScalar(scale, SkScalarAbs(l[i]));
584 scale = SkMaxScalar(scale, SkScalarAbs(m[i]));
585 }
586 GrAssert(scale > 0);
587 scale /= 10.0f;
588 k[0] /= scale;
589 k[1] /= scale;
590 k[2] /= scale;
591 l[0] /= scale;
592 l[1] /= scale;
593 l[2] /= scale;
594 m[0] /= scale;
595 m[1] /= scale;
596 m[2] /= scale;
597}
598
599// Equations based off of Loop-Blinn Quadratic GPU Rendering
egdaniel@google.com5383a752013-07-12 20:15:34 +0000600// Input Parametric:
601// 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)
602// Output Implicit:
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000603// f(x, y, w) = f(P) = K^2 - LM
604// K = dot(k, P), L = dot(l, P), M = dot(m, P)
605// k, l, m are calculated in function calc_conic_klm
egdaniel@google.com5383a752013-07-12 20:15:34 +0000606void set_conic_coeffs(const SkPoint p[3], Vertex verts[kVertsPerQuad], const float weight) {
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000607 SkScalar k[3];
608 SkScalar l[3];
609 SkScalar m[3];
610
611 calc_conic_klm(p, weight, k, l, m);
egdaniel@google.com5383a752013-07-12 20:15:34 +0000612
613 for (int i = 0; i < kVertsPerQuad; ++i) {
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000614 const SkPoint pnt = verts[i].fPos;
615 verts[i].fConic.fK = pnt.fX * k[0] + pnt.fY * k[1] + k[2];
616 verts[i].fConic.fL = pnt.fX * l[0] + pnt.fY * l[1] + l[2];
617 verts[i].fConic.fM = pnt.fX * m[0] + pnt.fY * m[1] + m[2];
egdaniel@google.com5383a752013-07-12 20:15:34 +0000618 }
619}
620
621void add_conics(const SkPoint p[3],
622 float weight,
623 const SkMatrix* toDevice,
624 const SkMatrix* toSrc,
625 Vertex** vert,
626 SkRect* devBounds) {
627 bloat_quad(p, toDevice, toSrc, *vert, devBounds);
628 set_conic_coeffs(p, *vert, weight);
629 *vert += kVertsPerQuad;
630}
631
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000632void add_quads(const SkPoint p[3],
633 int subdiv,
bsalomon@google.comb9086a02012-11-01 18:02:54 +0000634 const SkMatrix* toDevice,
635 const SkMatrix* toSrc,
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +0000636 Vertex** vert,
637 SkRect* devBounds) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000638 GrAssert(subdiv >= 0);
639 if (subdiv) {
640 SkPoint newP[5];
641 SkChopQuadAtHalf(p, newP);
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +0000642 add_quads(newP + 0, subdiv-1, toDevice, toSrc, vert, devBounds);
643 add_quads(newP + 2, subdiv-1, toDevice, toSrc, vert, devBounds);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000644 } else {
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +0000645 bloat_quad(p, toDevice, toSrc, *vert, devBounds);
egdaniel@google.com34b05ca2013-08-05 20:43:12 +0000646 set_uv_quad(p, *vert);
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000647 *vert += kVertsPerQuad;
648 }
649}
650
651void add_line(const SkPoint p[2],
652 int rtHeight,
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000653 const SkMatrix* toSrc,
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000654 Vertex** vert) {
655 const SkPoint& a = p[0];
656 const SkPoint& b = p[1];
657
658 SkVector orthVec = b;
659 orthVec -= a;
660
661 if (orthVec.setLength(SK_Scalar1)) {
662 orthVec.setOrthog(orthVec);
663
bsalomon@google.com706f6682012-10-23 14:53:55 +0000664 SkScalar lineC = -(a.dot(orthVec));
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000665 for (int i = 0; i < kVertsPerLineSeg; ++i) {
666 (*vert)[i].fPos = (i < 2) ? a : b;
667 if (0 == i || 3 == i) {
668 (*vert)[i].fPos -= orthVec;
669 } else {
670 (*vert)[i].fPos += orthVec;
671 }
bsalomon@google.com706f6682012-10-23 14:53:55 +0000672 (*vert)[i].fLine.fA = orthVec.fX;
673 (*vert)[i].fLine.fB = orthVec.fY;
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000674 (*vert)[i].fLine.fC = lineC;
675 }
bsalomon@google.comdbeeac32011-09-12 14:59:34 +0000676 if (NULL != toSrc) {
677 toSrc->mapPointsWithStride(&(*vert)->fPos,
678 sizeof(Vertex),
679 kVertsPerLineSeg);
680 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +0000681 } else {
682 // just make it degenerate and likely offscreen
683 (*vert)[0].fPos.set(SK_ScalarMax, SK_ScalarMax);
684 (*vert)[1].fPos.set(SK_ScalarMax, SK_ScalarMax);
685 (*vert)[2].fPos.set(SK_ScalarMax, SK_ScalarMax);
686 (*vert)[3].fPos.set(SK_ScalarMax, SK_ScalarMax);
687 }
688
689 *vert += kVertsPerLineSeg;
690}
691
692}
693
egdaniel@google.com5383a752013-07-12 20:15:34 +0000694/**
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000695 * Shader is based off of Loop-Blinn Quadratic GPU Rendering
egdaniel@google.com5383a752013-07-12 20:15:34 +0000696 * The output of this effect is a hairline edge for conics.
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000697 * Conics specified by implicit equation K^2 - LM.
698 * K, L, and M, are the first three values of the vertex attribute,
699 * the fourth value is not used. Distance is calculated using a
700 * first order approximation from the taylor series.
egdaniel@google.com5383a752013-07-12 20:15:34 +0000701 * Coverage is max(0, 1-distance).
702 */
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000703
704/**
705 * Test were also run using a second order distance approximation.
706 * There were two versions of the second order approx. The first version
707 * is of roughly the form:
708 * f(q) = |f(p)| - ||f'(p)||*||q-p|| - ||f''(p)||*||q-p||^2.
709 * The second is similar:
710 * f(q) = |f(p)| + ||f'(p)||*||q-p|| + ||f''(p)||*||q-p||^2.
711 * The exact version of the equations can be found in the paper
712 * "Distance Approximations for Rasterizing Implicit Curves" by Gabriel Taubin
713 *
714 * In both versions we solve the quadratic for ||q-p||.
715 * Version 1:
716 * gFM is magnitude of first partials and gFM2 is magnitude of 2nd partials (as derived from paper)
717 * builder->fsCodeAppend("\t\tedgeAlpha = (sqrt(gFM*gFM+4.0*func*gF2M) - gFM)/(2.0*gF2M);\n");
718 * Version 2:
719 * builder->fsCodeAppend("\t\tedgeAlpha = (gFM - sqrt(gFM*gFM-4.0*func*gF2M))/(2.0*gF2M);\n");
720 *
721 * Also note that 2nd partials of k,l,m are zero
722 *
723 * When comparing the two second order approximations to the first order approximations,
724 * the following results were found. Version 1 tends to underestimate the distances, thus it
725 * basically increases all the error that we were already seeing in the first order
726 * approx. So this version is not the one to use. Version 2 has the opposite effect
727 * and tends to overestimate the distances. This is much closer to what we are
728 * looking for. It is able to render ellipses (even thin ones) without the need to chop.
729 * However, it can not handle thin hyperbolas well and thus would still rely on
730 * chopping to tighten the clipping. Another side effect of the overestimating is
731 * that the curves become much thinner and "ropey". If all that was ever rendered
732 * were "not too thin" curves and ellipses then 2nd order may have an advantage since
733 * only one geometry would need to be rendered. However no benches were run comparing
734 * chopped first order and non chopped 2nd order.
735 */
egdaniel@google.com5383a752013-07-12 20:15:34 +0000736class HairConicEdgeEffect : public GrEffect {
737public:
738 static GrEffectRef* Create() {
739 GR_CREATE_STATIC_EFFECT(gHairConicEdgeEffect, HairConicEdgeEffect, ());
740 gHairConicEdgeEffect->ref();
741 return gHairConicEdgeEffect;
742 }
743
744 virtual ~HairConicEdgeEffect() {}
745
746 static const char* Name() { return "HairConicEdge"; }
747
748 virtual void getConstantColorComponents(GrColor* color,
749 uint32_t* validFlags) const SK_OVERRIDE {
750 *validFlags = 0;
751 }
752
753 virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
754 return GrTBackendEffectFactory<HairConicEdgeEffect>::getInstance();
755 }
756
757 class GLEffect : public GrGLEffect {
758 public:
759 GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
760 : INHERITED (factory) {}
761
762 virtual void emitCode(GrGLShaderBuilder* builder,
763 const GrDrawEffect& drawEffect,
764 EffectKey key,
765 const char* outputColor,
766 const char* inputColor,
767 const TextureSamplerArray& samplers) SK_OVERRIDE {
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000768 const char *vsName, *fsName;
egdaniel@google.com5383a752013-07-12 20:15:34 +0000769
770 SkAssertResult(builder->enableFeature(
771 GrGLShaderBuilder::kStandardDerivatives_GLSLFeature));
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000772 builder->addVarying(kVec4f_GrSLType, "ConicCoeffs",
773 &vsName, &fsName);
egdaniel@google.com5383a752013-07-12 20:15:34 +0000774 const SkString* attr0Name =
775 builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]);
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000776 builder->vsCodeAppendf("\t%s = %s;\n", vsName, attr0Name->c_str());
egdaniel@google.com5383a752013-07-12 20:15:34 +0000777
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000778 builder->fsCodeAppend("\t\tfloat edgeAlpha;\n");
egdaniel@google.com5383a752013-07-12 20:15:34 +0000779
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000780 builder->fsCodeAppendf("\t\tvec3 dklmdx = dFdx(%s.xyz);\n", fsName);
781 builder->fsCodeAppendf("\t\tvec3 dklmdy = dFdy(%s.xyz);\n", fsName);
782 builder->fsCodeAppendf("\t\tfloat dfdx =\n"
783 "\t\t\t2.0*%s.x*dklmdx.x - %s.y*dklmdx.z - %s.z*dklmdx.y;\n",
784 fsName, fsName, fsName);
785 builder->fsCodeAppendf("\t\tfloat dfdy =\n"
786 "\t\t\t2.0*%s.x*dklmdy.x - %s.y*dklmdy.z - %s.z*dklmdy.y;\n",
787 fsName, fsName, fsName);
788 builder->fsCodeAppend("\t\tvec2 gF = vec2(dfdx, dfdy);\n");
789 builder->fsCodeAppend("\t\tfloat gFM = sqrt(dot(gF, gF));\n");
790 builder->fsCodeAppendf("\t\tfloat func = abs(%s.x*%s.x - %s.y*%s.z);\n", fsName, fsName,
791 fsName, fsName);
792 builder->fsCodeAppend("\t\tedgeAlpha = func / gFM;\n");
793 builder->fsCodeAppend("\t\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
egdaniel@google.com5383a752013-07-12 20:15:34 +0000794 // Add line below for smooth cubic ramp
795 // builder->fsCodeAppend("\t\tedgeAlpha = edgeAlpha*edgeAlpha*(3.0-2.0*edgeAlpha);\n");
796
797 SkString modulate;
798 GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha");
799 builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
800 }
801
802 static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
803 return 0x0;
804 }
805
806 virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE {}
807
808 private:
809 typedef GrGLEffect INHERITED;
810 };
811
812private:
813 HairConicEdgeEffect() {
814 this->addVertexAttrib(kVec4f_GrSLType);
egdaniel@google.com5383a752013-07-12 20:15:34 +0000815 }
816
817 virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE {
818 return true;
819 }
820
821 GR_DECLARE_EFFECT_TEST;
822
823 typedef GrEffect INHERITED;
824};
825
826GR_DEFINE_EFFECT_TEST(HairConicEdgeEffect);
827
828GrEffectRef* HairConicEdgeEffect::TestCreate(SkMWCRandom* random,
829 GrContext*,
830 const GrDrawTargetCaps& caps,
831 GrTexture*[]) {
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +0000832 return caps.shaderDerivativeSupport() ? HairConicEdgeEffect::Create() : NULL;
egdaniel@google.com5383a752013-07-12 20:15:34 +0000833}
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000834
835/**
836 * The output of this effect is a hairline edge for quadratics.
837 * Quadratic specified by 0=u^2-v canonical coords. u and v are the first
838 * two components of the vertex attribute. Uses unsigned distance.
skia.committer@gmail.com041e2db2013-04-03 07:01:14 +0000839 * Coverage is min(0, 1-distance). 3rd & 4th component unused.
840 * Requires shader derivative instruction support.
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000841 */
842class HairQuadEdgeEffect : public GrEffect {
843public:
844
845 static GrEffectRef* Create() {
bsalomon@google.comd42aca32013-04-23 15:37:27 +0000846 GR_CREATE_STATIC_EFFECT(gHairQuadEdgeEffect, HairQuadEdgeEffect, ());
847 gHairQuadEdgeEffect->ref();
848 return gHairQuadEdgeEffect;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000849 }
850
851 virtual ~HairQuadEdgeEffect() {}
852
853 static const char* Name() { return "HairQuadEdge"; }
854
skia.committer@gmail.com041e2db2013-04-03 07:01:14 +0000855 virtual void getConstantColorComponents(GrColor* color,
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000856 uint32_t* validFlags) const SK_OVERRIDE {
857 *validFlags = 0;
858 }
859
860 virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
861 return GrTBackendEffectFactory<HairQuadEdgeEffect>::getInstance();
862 }
863
864 class GLEffect : public GrGLEffect {
865 public:
866 GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
867 : INHERITED (factory) {}
868
869 virtual void emitCode(GrGLShaderBuilder* builder,
870 const GrDrawEffect& drawEffect,
871 EffectKey key,
872 const char* outputColor,
873 const char* inputColor,
874 const TextureSamplerArray& samplers) SK_OVERRIDE {
875 const char *vsName, *fsName;
876 const SkString* attrName =
877 builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]);
878 builder->fsCodeAppendf("\t\tfloat edgeAlpha;\n");
879
880 SkAssertResult(builder->enableFeature(
egdaniel@google.com5383a752013-07-12 20:15:34 +0000881 GrGLShaderBuilder::kStandardDerivatives_GLSLFeature));
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000882 builder->addVarying(kVec4f_GrSLType, "HairQuadEdge", &vsName, &fsName);
883
884 builder->fsCodeAppendf("\t\tvec2 duvdx = dFdx(%s.xy);\n", fsName);
885 builder->fsCodeAppendf("\t\tvec2 duvdy = dFdy(%s.xy);\n", fsName);
886 builder->fsCodeAppendf("\t\tvec2 gF = vec2(2.0*%s.x*duvdx.x - duvdx.y,\n"
887 "\t\t 2.0*%s.x*duvdy.x - duvdy.y);\n",
888 fsName, fsName);
889 builder->fsCodeAppendf("\t\tedgeAlpha = (%s.x*%s.x - %s.y);\n", fsName, fsName,
890 fsName);
891 builder->fsCodeAppend("\t\tedgeAlpha = sqrt(edgeAlpha*edgeAlpha / dot(gF, gF));\n");
892 builder->fsCodeAppend("\t\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
893
894 SkString modulate;
bsalomon@google.com018f1792013-04-18 19:36:09 +0000895 GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha");
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000896 builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
897
898 builder->vsCodeAppendf("\t%s = %s;\n", vsName, attrName->c_str());
899 }
900
901 static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
902 return 0x0;
903 }
904
905 virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE {}
906
907 private:
908 typedef GrGLEffect INHERITED;
909 };
910
911private:
skia.committer@gmail.com041e2db2013-04-03 07:01:14 +0000912 HairQuadEdgeEffect() {
913 this->addVertexAttrib(kVec4f_GrSLType);
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000914 }
915
916 virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE {
917 return true;
918 }
919
920 GR_DECLARE_EFFECT_TEST;
921
922 typedef GrEffect INHERITED;
923};
924
925GR_DEFINE_EFFECT_TEST(HairQuadEdgeEffect);
926
927GrEffectRef* HairQuadEdgeEffect::TestCreate(SkMWCRandom* random,
928 GrContext*,
929 const GrDrawTargetCaps& caps,
930 GrTexture*[]) {
931 // Doesn't work without derivative instructions.
egdaniel@google.com5383a752013-07-12 20:15:34 +0000932 return caps.shaderDerivativeSupport() ? HairQuadEdgeEffect::Create() : NULL;
933}
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000934
935///////////////////////////////////////////////////////////////////////////////
936
937/**
skia.committer@gmail.com041e2db2013-04-03 07:01:14 +0000938 * The output of this effect is a 1-pixel wide line.
939 * Input is 2D implicit device coord line eq (a*x + b*y +c = 0). 4th component unused.
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000940 */
941class HairLineEdgeEffect : public GrEffect {
942public:
943
944 static GrEffectRef* Create() {
bsalomon@google.comd42aca32013-04-23 15:37:27 +0000945 GR_CREATE_STATIC_EFFECT(gHairLineEdge, HairLineEdgeEffect, ());
946 gHairLineEdge->ref();
947 return gHairLineEdge;
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000948 }
949
950 virtual ~HairLineEdgeEffect() {}
951
952 static const char* Name() { return "HairLineEdge"; }
953
skia.committer@gmail.com041e2db2013-04-03 07:01:14 +0000954 virtual void getConstantColorComponents(GrColor* color,
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000955 uint32_t* validFlags) const SK_OVERRIDE {
956 *validFlags = 0;
957 }
958
959 virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
960 return GrTBackendEffectFactory<HairLineEdgeEffect>::getInstance();
961 }
962
963 class GLEffect : public GrGLEffect {
964 public:
965 GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&)
966 : INHERITED (factory) {}
967
968 virtual void emitCode(GrGLShaderBuilder* builder,
969 const GrDrawEffect& drawEffect,
970 EffectKey key,
971 const char* outputColor,
972 const char* inputColor,
973 const TextureSamplerArray& samplers) SK_OVERRIDE {
974 const char *vsName, *fsName;
975 const SkString* attrName =
976 builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]);
977 builder->fsCodeAppendf("\t\tfloat edgeAlpha;\n");
978
979 builder->addVarying(kVec4f_GrSLType, "HairLineEdge", &vsName, &fsName);
980
981 builder->fsCodeAppendf("\t\tedgeAlpha = abs(dot(vec3(%s.xy,1), %s.xyz));\n",
982 builder->fragmentPosition(), fsName);
983 builder->fsCodeAppendf("\t\tedgeAlpha = max(1.0 - edgeAlpha, 0.0);\n");
984
985 SkString modulate;
bsalomon@google.com018f1792013-04-18 19:36:09 +0000986 GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha");
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +0000987 builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str());
988
989 builder->vsCodeAppendf("\t%s = %s;\n", vsName, attrName->c_str());
990 }
991
992 static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) {
993 return 0x0;
994 }
995
996 virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE {}
997
998 private:
999 typedef GrGLEffect INHERITED;
1000 };
1001
1002private:
skia.committer@gmail.com041e2db2013-04-03 07:01:14 +00001003 HairLineEdgeEffect() {
1004 this->addVertexAttrib(kVec4f_GrSLType);
commit-bot@chromium.org8d47ddc2013-05-09 14:55:46 +00001005 this->setWillReadFragmentPosition();
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +00001006 }
1007
1008 virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE {
1009 return true;
1010 }
1011
1012 GR_DECLARE_EFFECT_TEST;
1013
1014 typedef GrEffect INHERITED;
1015};
1016
1017GR_DEFINE_EFFECT_TEST(HairLineEdgeEffect);
1018
1019GrEffectRef* HairLineEdgeEffect::TestCreate(SkMWCRandom* random,
1020 GrContext*,
1021 const GrDrawTargetCaps& caps,
1022 GrTexture*[]) {
1023 return HairLineEdgeEffect::Create();
1024}
1025
1026///////////////////////////////////////////////////////////////////////////////
1027
robertphillips@google.com42903302013-04-20 12:26:07 +00001028namespace {
1029
1030// position + edge
1031extern const GrVertexAttrib gHairlineAttribs[] = {
1032 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding},
1033 {kVec4f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding}
1034};
robertphillips@google.com42903302013-04-20 12:26:07 +00001035};
1036
bsalomon@google.comb3729422012-03-07 19:13:28 +00001037bool GrAAHairLinePathRenderer::createGeom(
1038 const SkPath& path,
bsalomon@google.comb3729422012-03-07 19:13:28 +00001039 GrDrawTarget* target,
bsalomon@google.comb3729422012-03-07 19:13:28 +00001040 int* lineCnt,
1041 int* quadCnt,
egdaniel@google.com5383a752013-07-12 20:15:34 +00001042 int* conicCnt,
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +00001043 GrDrawTarget::AutoReleaseGeometry* arg,
1044 SkRect* devBounds) {
jvanverth@google.com9b855c72013-03-01 18:21:22 +00001045 GrDrawState* drawState = target->drawState();
1046 int rtHeight = drawState->getRenderTarget()->height();
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001047
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +00001048 SkIRect devClipBounds;
commit-bot@chromium.org912e68e2013-05-24 18:51:55 +00001049 target->getClip()->getConservativeBounds(drawState->getRenderTarget(), &devClipBounds);
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001050
jvanverth@google.com9b855c72013-03-01 18:21:22 +00001051 SkMatrix viewM = drawState->getViewMatrix();
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001052
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +00001053 // All the vertices that we compute are within 1 of path control points with the exception of
1054 // one of the bounding vertices for each quad. The add_quads() function will update the bounds
1055 // for each quad added.
1056 *devBounds = path.getBounds();
1057 viewM.mapRect(devBounds);
1058 devBounds->outset(SK_Scalar1, SK_Scalar1);
1059
bsalomon@google.com92669012011-09-27 19:10:05 +00001060 PREALLOC_PTARRAY(128) lines;
1061 PREALLOC_PTARRAY(128) quads;
egdaniel@google.com5383a752013-07-12 20:15:34 +00001062 PREALLOC_PTARRAY(128) conics;
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001063 IntArray qSubdivs;
egdaniel@google.com5383a752013-07-12 20:15:34 +00001064 FloatArray cWeights;
bsalomon@google.com0f11e1a2012-10-08 14:48:36 +00001065 *quadCnt = generate_lines_and_quads(path, viewM, devClipBounds,
egdaniel@google.com5383a752013-07-12 20:15:34 +00001066 &lines, &quads, &conics, &qSubdivs, &cWeights);
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001067
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001068 *lineCnt = lines.count() / 2;
egdaniel@google.com5383a752013-07-12 20:15:34 +00001069 *conicCnt = conics.count() / 3;
1070 int vertCnt = kVertsPerLineSeg * *lineCnt + kVertsPerQuad * *quadCnt +
1071 kVertsPerQuad * *conicCnt;
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001072
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +00001073 target->drawState()->setVertexAttribs<gHairlineAttribs>(SK_ARRAY_COUNT(gHairlineAttribs));
jvanverth@google.comb75b0a02013-02-05 20:33:30 +00001074 GrAssert(sizeof(Vertex) == target->getDrawState().getVertexSize());
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001075
jvanverth@google.comb75b0a02013-02-05 20:33:30 +00001076 if (!arg->set(target, vertCnt, 0)) {
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001077 return false;
1078 }
bsalomon@google.comdbeeac32011-09-12 14:59:34 +00001079
bsalomon@google.comb3729422012-03-07 19:13:28 +00001080 Vertex* verts = reinterpret_cast<Vertex*>(arg->vertices());
1081
bsalomon@google.comb9086a02012-11-01 18:02:54 +00001082 const SkMatrix* toDevice = NULL;
1083 const SkMatrix* toSrc = NULL;
1084 SkMatrix ivm;
bsalomon@google.comdbeeac32011-09-12 14:59:34 +00001085
1086 if (viewM.hasPerspective()) {
1087 if (viewM.invert(&ivm)) {
1088 toDevice = &viewM;
1089 toSrc = &ivm;
1090 }
1091 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001092
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001093 for (int i = 0; i < *lineCnt; ++i) {
bsalomon@google.comdbeeac32011-09-12 14:59:34 +00001094 add_line(&lines[2*i], rtHeight, toSrc, &verts);
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001095 }
bsalomon@google.comdbeeac32011-09-12 14:59:34 +00001096
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001097 int unsubdivQuadCnt = quads.count() / 3;
1098 for (int i = 0; i < unsubdivQuadCnt; ++i) {
1099 GrAssert(qSubdivs[i] >= 0);
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +00001100 add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts, devBounds);
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001101 }
1102
egdaniel@google.com5383a752013-07-12 20:15:34 +00001103 // Start Conics
1104 for (int i = 0; i < *conicCnt; ++i) {
1105 add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &verts, devBounds);
1106 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001107 return true;
1108}
1109
robertphillips@google.com8a4fc402012-05-24 12:42:24 +00001110bool GrAAHairLinePathRenderer::canDrawPath(const SkPath& path,
sugoi@google.com5f74cf82012-12-17 21:16:45 +00001111 const SkStrokeRec& stroke,
robertphillips@google.com8a4fc402012-05-24 12:42:24 +00001112 const GrDrawTarget* target,
1113 bool antiAlias) const {
sugoi@google.com5f74cf82012-12-17 21:16:45 +00001114 if (!stroke.isHairlineStyle() || !antiAlias) {
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001115 return false;
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001116 }
1117
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +00001118 if (SkPath::kLine_SegmentMask == path.getSegmentMasks() ||
1119 target->caps()->shaderDerivativeSupport()) {
1120 return true;
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001121 }
egdaniel@google.com3f2a2d52013-08-01 17:09:11 +00001122 return false;
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001123}
1124
1125bool GrAAHairLinePathRenderer::onDrawPath(const SkPath& path,
sugoi@google.com5f74cf82012-12-17 21:16:45 +00001126 const SkStrokeRec&,
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001127 GrDrawTarget* target,
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001128 bool antiAlias) {
1129
1130 int lineCnt;
1131 int quadCnt;
egdaniel@google.com5383a752013-07-12 20:15:34 +00001132 int conicCnt;
bsalomon@google.comb3729422012-03-07 19:13:28 +00001133 GrDrawTarget::AutoReleaseGeometry arg;
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +00001134 SkRect devBounds;
1135
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001136 if (!this->createGeom(path,
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001137 target,
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001138 &lineCnt,
bsalomon@google.comb3729422012-03-07 19:13:28 +00001139 &quadCnt,
egdaniel@google.com5383a752013-07-12 20:15:34 +00001140 &conicCnt,
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +00001141 &arg,
1142 &devBounds)) {
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001143 return false;
1144 }
1145
bsalomon@google.com137f1342013-05-29 21:27:53 +00001146 GrDrawTarget::AutoStateRestore asr;
bsalomon@google.com4647f902013-03-26 14:45:27 +00001147
bsalomon@google.coma8347462012-10-08 18:59:39 +00001148 // createGeom transforms the geometry to device space when the matrix does not have
1149 // perspective.
bsalomon@google.com137f1342013-05-29 21:27:53 +00001150 if (target->getDrawState().getViewMatrix().hasPerspective()) {
1151 asr.set(target, GrDrawTarget::kPreserve_ASRInit);
1152 } else if (!asr.setIdentity(target, GrDrawTarget::kPreserve_ASRInit)) {
1153 return false;
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001154 }
bsalomon@google.com137f1342013-05-29 21:27:53 +00001155 GrDrawState* drawState = target->drawState();
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001156
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001157 // TODO: See whether rendering lines as degenerate quads improves perf
1158 // when we have a mix
bsalomon@google.coma8347462012-10-08 18:59:39 +00001159
bsalomon@google.com4647f902013-03-26 14:45:27 +00001160 static const int kEdgeAttrIndex = 1;
bsalomon@google.coma8347462012-10-08 18:59:39 +00001161
commit-bot@chromium.org90c240a2013-04-02 17:57:21 +00001162 GrEffectRef* hairLineEffect = HairLineEdgeEffect::Create();
1163 GrEffectRef* hairQuadEffect = HairQuadEdgeEffect::Create();
egdaniel@google.com5383a752013-07-12 20:15:34 +00001164 GrEffectRef* hairConicEffect = HairConicEdgeEffect::Create();
skia.committer@gmail.com37cbc7f2013-03-27 07:01:04 +00001165
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +00001166 // Check devBounds
1167#if GR_DEBUG
1168 SkRect tolDevBounds = devBounds;
1169 tolDevBounds.outset(SK_Scalar1 / 10000, SK_Scalar1 / 10000);
1170 SkRect actualBounds;
1171 Vertex* verts = reinterpret_cast<Vertex*>(arg.vertices());
egdaniel@google.com5383a752013-07-12 20:15:34 +00001172 int vCount = kVertsPerLineSeg * lineCnt + kVertsPerQuad * quadCnt + kVertsPerQuad * conicCnt;
bsalomon@google.com1dd9baa2013-05-20 16:49:06 +00001173 bool first = true;
1174 for (int i = 0; i < vCount; ++i) {
1175 SkPoint pos = verts[i].fPos;
1176 // This is a hack to workaround the fact that we move some degenerate segments offscreen.
1177 if (SK_ScalarMax == pos.fX) {
1178 continue;
1179 }
1180 drawState->getViewMatrix().mapPoints(&pos, 1);
1181 if (first) {
1182 actualBounds.set(pos.fX, pos.fY, pos.fX, pos.fY);
1183 first = false;
1184 } else {
1185 actualBounds.growToInclude(pos.fX, pos.fY);
1186 }
1187 }
1188 if (!first) {
1189 GrAssert(tolDevBounds.contains(actualBounds));
1190 }
1191#endif
1192
bsalomon@google.comeb6879f2013-06-13 19:34:18 +00001193 {
1194 GrDrawState::AutoRestoreEffects are(drawState);
1195 target->setIndexSourceToBuffer(fLinesIndexBuffer);
1196 int lines = 0;
1197 int nBufLines = fLinesIndexBuffer->maxQuads();
1198 drawState->addCoverageEffect(hairLineEffect, kEdgeAttrIndex)->unref();
1199 while (lines < lineCnt) {
1200 int n = GrMin(lineCnt - lines, nBufLines);
1201 target->drawIndexed(kTriangles_GrPrimitiveType,
1202 kVertsPerLineSeg*lines, // startV
1203 0, // startI
1204 kVertsPerLineSeg*n, // vCount
1205 kIdxsPerLineSeg*n,
1206 &devBounds); // iCount
1207 lines += n;
1208 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001209 }
1210
egdaniel@google.com5383a752013-07-12 20:15:34 +00001211 {
1212 GrDrawState::AutoRestoreEffects are(drawState);
1213 target->setIndexSourceToBuffer(fQuadsIndexBuffer);
1214 int quads = 0;
1215 drawState->addCoverageEffect(hairQuadEffect, kEdgeAttrIndex)->unref();
1216 while (quads < quadCnt) {
1217 int n = GrMin(quadCnt - quads, kNumQuadsInIdxBuffer);
1218 target->drawIndexed(kTriangles_GrPrimitiveType,
1219 kVertsPerLineSeg * lineCnt + kVertsPerQuad*quads, // startV
1220 0, // startI
1221 kVertsPerQuad*n, // vCount
1222 kIdxsPerQuad*n, // iCount
1223 &devBounds);
1224 quads += n;
1225 }
1226 }
1227
1228 {
1229 GrDrawState::AutoRestoreEffects are(drawState);
1230 int conics = 0;
1231 drawState->addCoverageEffect(hairConicEffect, 1, 2)->unref();
1232 while (conics < conicCnt) {
1233 int n = GrMin(conicCnt - conics, kNumQuadsInIdxBuffer);
1234 target->drawIndexed(kTriangles_GrPrimitiveType,
1235 kVertsPerLineSeg*lineCnt +
1236 kVertsPerQuad*(quadCnt + conics), // startV
1237 0, // startI
1238 kVertsPerQuad*n, // vCount
1239 kIdxsPerQuad*n, // iCount
1240 &devBounds);
1241 conics += n;
1242 }
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001243 }
bsalomon@google.com0406b9e2013-04-02 21:00:15 +00001244 target->resetIndexSource();
bsalomon@google.com4647f902013-03-26 14:45:27 +00001245
bsalomon@google.comc2099d22012-03-02 21:26:50 +00001246 return true;
bsalomon@google.comaeb21602011-08-30 18:13:44 +00001247}