1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/skia/trunk/src/gpu/GrAAHairLinePathRenderer.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1042 @@ 1.4 +/* 1.5 + * Copyright 2011 Google Inc. 1.6 + * 1.7 + * Use of this source code is governed by a BSD-style license that can be 1.8 + * found in the LICENSE file. 1.9 + */ 1.10 + 1.11 +#include "GrAAHairLinePathRenderer.h" 1.12 + 1.13 +#include "GrContext.h" 1.14 +#include "GrDrawState.h" 1.15 +#include "GrDrawTargetCaps.h" 1.16 +#include "GrEffect.h" 1.17 +#include "GrGpu.h" 1.18 +#include "GrIndexBuffer.h" 1.19 +#include "GrPathUtils.h" 1.20 +#include "GrTBackendEffectFactory.h" 1.21 +#include "SkGeometry.h" 1.22 +#include "SkStroke.h" 1.23 +#include "SkTemplates.h" 1.24 + 1.25 +#include "effects/GrBezierEffect.h" 1.26 + 1.27 +namespace { 1.28 +// quadratics are rendered as 5-sided polys in order to bound the 1.29 +// AA stroke around the center-curve. See comments in push_quad_index_buffer and 1.30 +// bloat_quad. Quadratics and conics share an index buffer 1.31 +static const int kVertsPerQuad = 5; 1.32 +static const int kIdxsPerQuad = 9; 1.33 + 1.34 +// lines are rendered as: 1.35 +// *______________* 1.36 +// |\ -_______ /| 1.37 +// | \ \ / | 1.38 +// | *--------* | 1.39 +// | / ______/ \ | 1.40 +// */_-__________\* 1.41 +// For: 6 vertices and 18 indices (for 6 triangles) 1.42 +static const int kVertsPerLineSeg = 6; 1.43 +static const int kIdxsPerLineSeg = 18; 1.44 + 1.45 +static const int kNumQuadsInIdxBuffer = 256; 1.46 +static const size_t kQuadIdxSBufize = kIdxsPerQuad * 1.47 + sizeof(uint16_t) * 1.48 + kNumQuadsInIdxBuffer; 1.49 + 1.50 +static const int kNumLineSegsInIdxBuffer = 256; 1.51 +static const size_t kLineSegIdxSBufize = kIdxsPerLineSeg * 1.52 + sizeof(uint16_t) * 1.53 + kNumLineSegsInIdxBuffer; 1.54 + 1.55 +static bool push_quad_index_data(GrIndexBuffer* qIdxBuffer) { 1.56 + uint16_t* data = (uint16_t*) qIdxBuffer->lock(); 1.57 + bool tempData = NULL == data; 1.58 + if (tempData) { 1.59 + data = SkNEW_ARRAY(uint16_t, kNumQuadsInIdxBuffer * kIdxsPerQuad); 1.60 + } 1.61 + for (int i = 0; i < kNumQuadsInIdxBuffer; ++i) { 1.62 + 1.63 + // Each quadratic is rendered as a five sided polygon. This poly bounds 1.64 + // the quadratic's bounding triangle but has been expanded so that the 1.65 + // 1-pixel wide area around the curve is inside the poly. 1.66 + // If a,b,c are the original control points then the poly a0,b0,c0,c1,a1 1.67 + // that is rendered would look like this: 1.68 + // b0 1.69 + // b 1.70 + // 1.71 + // a0 c0 1.72 + // a c 1.73 + // a1 c1 1.74 + // Each is drawn as three triangles specified by these 9 indices: 1.75 + int baseIdx = i * kIdxsPerQuad; 1.76 + uint16_t baseVert = (uint16_t)(i * kVertsPerQuad); 1.77 + data[0 + baseIdx] = baseVert + 0; // a0 1.78 + data[1 + baseIdx] = baseVert + 1; // a1 1.79 + data[2 + baseIdx] = baseVert + 2; // b0 1.80 + data[3 + baseIdx] = baseVert + 2; // b0 1.81 + data[4 + baseIdx] = baseVert + 4; // c1 1.82 + data[5 + baseIdx] = baseVert + 3; // c0 1.83 + data[6 + baseIdx] = baseVert + 1; // a1 1.84 + data[7 + baseIdx] = baseVert + 4; // c1 1.85 + data[8 + baseIdx] = baseVert + 2; // b0 1.86 + } 1.87 + if (tempData) { 1.88 + bool ret = qIdxBuffer->updateData(data, kQuadIdxSBufize); 1.89 + delete[] data; 1.90 + return ret; 1.91 + } else { 1.92 + qIdxBuffer->unlock(); 1.93 + return true; 1.94 + } 1.95 +} 1.96 + 1.97 +static bool push_line_index_data(GrIndexBuffer* lIdxBuffer) { 1.98 + uint16_t* data = (uint16_t*) lIdxBuffer->lock(); 1.99 + bool tempData = NULL == data; 1.100 + if (tempData) { 1.101 + data = SkNEW_ARRAY(uint16_t, kNumLineSegsInIdxBuffer * kIdxsPerLineSeg); 1.102 + } 1.103 + for (int i = 0; i < kNumLineSegsInIdxBuffer; ++i) { 1.104 + // Each line segment is rendered as two quads and two triangles. 1.105 + // p0 and p1 have alpha = 1 while all other points have alpha = 0. 1.106 + // The four external points are offset 1 pixel perpendicular to the 1.107 + // line and half a pixel parallel to the line. 1.108 + // 1.109 + // p4 p5 1.110 + // p0 p1 1.111 + // p2 p3 1.112 + // 1.113 + // Each is drawn as six triangles specified by these 18 indices: 1.114 + int baseIdx = i * kIdxsPerLineSeg; 1.115 + uint16_t baseVert = (uint16_t)(i * kVertsPerLineSeg); 1.116 + data[0 + baseIdx] = baseVert + 0; 1.117 + data[1 + baseIdx] = baseVert + 1; 1.118 + data[2 + baseIdx] = baseVert + 3; 1.119 + 1.120 + data[3 + baseIdx] = baseVert + 0; 1.121 + data[4 + baseIdx] = baseVert + 3; 1.122 + data[5 + baseIdx] = baseVert + 2; 1.123 + 1.124 + data[6 + baseIdx] = baseVert + 0; 1.125 + data[7 + baseIdx] = baseVert + 4; 1.126 + data[8 + baseIdx] = baseVert + 5; 1.127 + 1.128 + data[9 + baseIdx] = baseVert + 0; 1.129 + data[10+ baseIdx] = baseVert + 5; 1.130 + data[11+ baseIdx] = baseVert + 1; 1.131 + 1.132 + data[12 + baseIdx] = baseVert + 0; 1.133 + data[13 + baseIdx] = baseVert + 2; 1.134 + data[14 + baseIdx] = baseVert + 4; 1.135 + 1.136 + data[15 + baseIdx] = baseVert + 1; 1.137 + data[16 + baseIdx] = baseVert + 5; 1.138 + data[17 + baseIdx] = baseVert + 3; 1.139 + } 1.140 + if (tempData) { 1.141 + bool ret = lIdxBuffer->updateData(data, kLineSegIdxSBufize); 1.142 + delete[] data; 1.143 + return ret; 1.144 + } else { 1.145 + lIdxBuffer->unlock(); 1.146 + return true; 1.147 + } 1.148 +} 1.149 +} 1.150 + 1.151 +GrPathRenderer* GrAAHairLinePathRenderer::Create(GrContext* context) { 1.152 + GrGpu* gpu = context->getGpu(); 1.153 + GrIndexBuffer* qIdxBuf = gpu->createIndexBuffer(kQuadIdxSBufize, false); 1.154 + SkAutoTUnref<GrIndexBuffer> qIdxBuffer(qIdxBuf); 1.155 + if (NULL == qIdxBuf || !push_quad_index_data(qIdxBuf)) { 1.156 + return NULL; 1.157 + } 1.158 + GrIndexBuffer* lIdxBuf = gpu->createIndexBuffer(kLineSegIdxSBufize, false); 1.159 + SkAutoTUnref<GrIndexBuffer> lIdxBuffer(lIdxBuf); 1.160 + if (NULL == lIdxBuf || !push_line_index_data(lIdxBuf)) { 1.161 + return NULL; 1.162 + } 1.163 + return SkNEW_ARGS(GrAAHairLinePathRenderer, 1.164 + (context, lIdxBuf, qIdxBuf)); 1.165 +} 1.166 + 1.167 +GrAAHairLinePathRenderer::GrAAHairLinePathRenderer( 1.168 + const GrContext* context, 1.169 + const GrIndexBuffer* linesIndexBuffer, 1.170 + const GrIndexBuffer* quadsIndexBuffer) { 1.171 + fLinesIndexBuffer = linesIndexBuffer; 1.172 + linesIndexBuffer->ref(); 1.173 + fQuadsIndexBuffer = quadsIndexBuffer; 1.174 + quadsIndexBuffer->ref(); 1.175 +} 1.176 + 1.177 +GrAAHairLinePathRenderer::~GrAAHairLinePathRenderer() { 1.178 + fLinesIndexBuffer->unref(); 1.179 + fQuadsIndexBuffer->unref(); 1.180 +} 1.181 + 1.182 +namespace { 1.183 + 1.184 +#define PREALLOC_PTARRAY(N) SkSTArray<(N),SkPoint, true> 1.185 + 1.186 +// Takes 178th time of logf on Z600 / VC2010 1.187 +int get_float_exp(float x) { 1.188 + GR_STATIC_ASSERT(sizeof(int) == sizeof(float)); 1.189 +#ifdef SK_DEBUG 1.190 + static bool tested; 1.191 + if (!tested) { 1.192 + tested = true; 1.193 + SkASSERT(get_float_exp(0.25f) == -2); 1.194 + SkASSERT(get_float_exp(0.3f) == -2); 1.195 + SkASSERT(get_float_exp(0.5f) == -1); 1.196 + SkASSERT(get_float_exp(1.f) == 0); 1.197 + SkASSERT(get_float_exp(2.f) == 1); 1.198 + SkASSERT(get_float_exp(2.5f) == 1); 1.199 + SkASSERT(get_float_exp(8.f) == 3); 1.200 + SkASSERT(get_float_exp(100.f) == 6); 1.201 + SkASSERT(get_float_exp(1000.f) == 9); 1.202 + SkASSERT(get_float_exp(1024.f) == 10); 1.203 + SkASSERT(get_float_exp(3000000.f) == 21); 1.204 + } 1.205 +#endif 1.206 + const int* iptr = (const int*)&x; 1.207 + return (((*iptr) & 0x7f800000) >> 23) - 127; 1.208 +} 1.209 + 1.210 +// Uses the max curvature function for quads to estimate 1.211 +// where to chop the conic. If the max curvature is not 1.212 +// found along the curve segment it will return 1 and 1.213 +// dst[0] is the original conic. If it returns 2 the dst[0] 1.214 +// and dst[1] are the two new conics. 1.215 +int split_conic(const SkPoint src[3], SkConic dst[2], const SkScalar weight) { 1.216 + SkScalar t = SkFindQuadMaxCurvature(src); 1.217 + if (t == 0) { 1.218 + if (dst) { 1.219 + dst[0].set(src, weight); 1.220 + } 1.221 + return 1; 1.222 + } else { 1.223 + if (dst) { 1.224 + SkConic conic; 1.225 + conic.set(src, weight); 1.226 + conic.chopAt(t, dst); 1.227 + } 1.228 + return 2; 1.229 + } 1.230 +} 1.231 + 1.232 +// Calls split_conic on the entire conic and then once more on each subsection. 1.233 +// Most cases will result in either 1 conic (chop point is not within t range) 1.234 +// or 3 points (split once and then one subsection is split again). 1.235 +int chop_conic(const SkPoint src[3], SkConic dst[4], const SkScalar weight) { 1.236 + SkConic dstTemp[2]; 1.237 + int conicCnt = split_conic(src, dstTemp, weight); 1.238 + if (2 == conicCnt) { 1.239 + int conicCnt2 = split_conic(dstTemp[0].fPts, dst, dstTemp[0].fW); 1.240 + conicCnt = conicCnt2 + split_conic(dstTemp[1].fPts, &dst[conicCnt2], dstTemp[1].fW); 1.241 + } else { 1.242 + dst[0] = dstTemp[0]; 1.243 + } 1.244 + return conicCnt; 1.245 +} 1.246 + 1.247 +// returns 0 if quad/conic is degen or close to it 1.248 +// in this case approx the path with lines 1.249 +// otherwise returns 1 1.250 +int is_degen_quad_or_conic(const SkPoint p[3]) { 1.251 + static const SkScalar gDegenerateToLineTol = SK_Scalar1; 1.252 + static const SkScalar gDegenerateToLineTolSqd = 1.253 + SkScalarMul(gDegenerateToLineTol, gDegenerateToLineTol); 1.254 + 1.255 + if (p[0].distanceToSqd(p[1]) < gDegenerateToLineTolSqd || 1.256 + p[1].distanceToSqd(p[2]) < gDegenerateToLineTolSqd) { 1.257 + return 1; 1.258 + } 1.259 + 1.260 + SkScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]); 1.261 + if (dsqd < gDegenerateToLineTolSqd) { 1.262 + return 1; 1.263 + } 1.264 + 1.265 + if (p[2].distanceToLineBetweenSqd(p[1], p[0]) < gDegenerateToLineTolSqd) { 1.266 + return 1; 1.267 + } 1.268 + return 0; 1.269 +} 1.270 + 1.271 +// we subdivide the quads to avoid huge overfill 1.272 +// if it returns -1 then should be drawn as lines 1.273 +int num_quad_subdivs(const SkPoint p[3]) { 1.274 + static const SkScalar gDegenerateToLineTol = SK_Scalar1; 1.275 + static const SkScalar gDegenerateToLineTolSqd = 1.276 + SkScalarMul(gDegenerateToLineTol, gDegenerateToLineTol); 1.277 + 1.278 + if (p[0].distanceToSqd(p[1]) < gDegenerateToLineTolSqd || 1.279 + p[1].distanceToSqd(p[2]) < gDegenerateToLineTolSqd) { 1.280 + return -1; 1.281 + } 1.282 + 1.283 + SkScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]); 1.284 + if (dsqd < gDegenerateToLineTolSqd) { 1.285 + return -1; 1.286 + } 1.287 + 1.288 + if (p[2].distanceToLineBetweenSqd(p[1], p[0]) < gDegenerateToLineTolSqd) { 1.289 + return -1; 1.290 + } 1.291 + 1.292 + // tolerance of triangle height in pixels 1.293 + // tuned on windows Quadro FX 380 / Z600 1.294 + // trade off of fill vs cpu time on verts 1.295 + // maybe different when do this using gpu (geo or tess shaders) 1.296 + static const SkScalar gSubdivTol = 175 * SK_Scalar1; 1.297 + 1.298 + if (dsqd <= SkScalarMul(gSubdivTol, gSubdivTol)) { 1.299 + return 0; 1.300 + } else { 1.301 + static const int kMaxSub = 4; 1.302 + // subdividing the quad reduces d by 4. so we want x = log4(d/tol) 1.303 + // = log4(d*d/tol*tol)/2 1.304 + // = log2(d*d/tol*tol) 1.305 + 1.306 + // +1 since we're ignoring the mantissa contribution. 1.307 + int log = get_float_exp(dsqd/(gSubdivTol*gSubdivTol)) + 1; 1.308 + log = GrMin(GrMax(0, log), kMaxSub); 1.309 + return log; 1.310 + } 1.311 +} 1.312 + 1.313 +/** 1.314 + * Generates the lines and quads to be rendered. Lines are always recorded in 1.315 + * device space. We will do a device space bloat to account for the 1pixel 1.316 + * thickness. 1.317 + * Quads are recorded in device space unless m contains 1.318 + * perspective, then in they are in src space. We do this because we will 1.319 + * subdivide large quads to reduce over-fill. This subdivision has to be 1.320 + * performed before applying the perspective matrix. 1.321 + */ 1.322 +int generate_lines_and_quads(const SkPath& path, 1.323 + const SkMatrix& m, 1.324 + const SkIRect& devClipBounds, 1.325 + GrAAHairLinePathRenderer::PtArray* lines, 1.326 + GrAAHairLinePathRenderer::PtArray* quads, 1.327 + GrAAHairLinePathRenderer::PtArray* conics, 1.328 + GrAAHairLinePathRenderer::IntArray* quadSubdivCnts, 1.329 + GrAAHairLinePathRenderer::FloatArray* conicWeights) { 1.330 + SkPath::Iter iter(path, false); 1.331 + 1.332 + int totalQuadCount = 0; 1.333 + SkRect bounds; 1.334 + SkIRect ibounds; 1.335 + 1.336 + bool persp = m.hasPerspective(); 1.337 + 1.338 + for (;;) { 1.339 + GrPoint pathPts[4]; 1.340 + GrPoint devPts[4]; 1.341 + SkPath::Verb verb = iter.next(pathPts); 1.342 + switch (verb) { 1.343 + case SkPath::kConic_Verb: { 1.344 + SkConic dst[4]; 1.345 + // We chop the conics to create tighter clipping to hide error 1.346 + // that appears near max curvature of very thin conics. Thin 1.347 + // hyperbolas with high weight still show error. 1.348 + int conicCnt = chop_conic(pathPts, dst, iter.conicWeight()); 1.349 + for (int i = 0; i < conicCnt; ++i) { 1.350 + SkPoint* chopPnts = dst[i].fPts; 1.351 + m.mapPoints(devPts, chopPnts, 3); 1.352 + bounds.setBounds(devPts, 3); 1.353 + bounds.outset(SK_Scalar1, SK_Scalar1); 1.354 + bounds.roundOut(&ibounds); 1.355 + if (SkIRect::Intersects(devClipBounds, ibounds)) { 1.356 + if (is_degen_quad_or_conic(devPts)) { 1.357 + SkPoint* pts = lines->push_back_n(4); 1.358 + pts[0] = devPts[0]; 1.359 + pts[1] = devPts[1]; 1.360 + pts[2] = devPts[1]; 1.361 + pts[3] = devPts[2]; 1.362 + } else { 1.363 + // when in perspective keep conics in src space 1.364 + SkPoint* cPts = persp ? chopPnts : devPts; 1.365 + SkPoint* pts = conics->push_back_n(3); 1.366 + pts[0] = cPts[0]; 1.367 + pts[1] = cPts[1]; 1.368 + pts[2] = cPts[2]; 1.369 + conicWeights->push_back() = dst[i].fW; 1.370 + } 1.371 + } 1.372 + } 1.373 + break; 1.374 + } 1.375 + case SkPath::kMove_Verb: 1.376 + break; 1.377 + case SkPath::kLine_Verb: 1.378 + m.mapPoints(devPts, pathPts, 2); 1.379 + bounds.setBounds(devPts, 2); 1.380 + bounds.outset(SK_Scalar1, SK_Scalar1); 1.381 + bounds.roundOut(&ibounds); 1.382 + if (SkIRect::Intersects(devClipBounds, ibounds)) { 1.383 + SkPoint* pts = lines->push_back_n(2); 1.384 + pts[0] = devPts[0]; 1.385 + pts[1] = devPts[1]; 1.386 + } 1.387 + break; 1.388 + case SkPath::kQuad_Verb: { 1.389 + SkPoint choppedPts[5]; 1.390 + // Chopping the quad helps when the quad is either degenerate or nearly degenerate. 1.391 + // When it is degenerate it allows the approximation with lines to work since the 1.392 + // chop point (if there is one) will be at the parabola's vertex. In the nearly 1.393 + // degenerate the QuadUVMatrix computed for the points is almost singular which 1.394 + // can cause rendering artifacts. 1.395 + int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts); 1.396 + for (int i = 0; i < n; ++i) { 1.397 + SkPoint* quadPts = choppedPts + i * 2; 1.398 + m.mapPoints(devPts, quadPts, 3); 1.399 + bounds.setBounds(devPts, 3); 1.400 + bounds.outset(SK_Scalar1, SK_Scalar1); 1.401 + bounds.roundOut(&ibounds); 1.402 + 1.403 + if (SkIRect::Intersects(devClipBounds, ibounds)) { 1.404 + int subdiv = num_quad_subdivs(devPts); 1.405 + SkASSERT(subdiv >= -1); 1.406 + if (-1 == subdiv) { 1.407 + SkPoint* pts = lines->push_back_n(4); 1.408 + pts[0] = devPts[0]; 1.409 + pts[1] = devPts[1]; 1.410 + pts[2] = devPts[1]; 1.411 + pts[3] = devPts[2]; 1.412 + } else { 1.413 + // when in perspective keep quads in src space 1.414 + SkPoint* qPts = persp ? quadPts : devPts; 1.415 + SkPoint* pts = quads->push_back_n(3); 1.416 + pts[0] = qPts[0]; 1.417 + pts[1] = qPts[1]; 1.418 + pts[2] = qPts[2]; 1.419 + quadSubdivCnts->push_back() = subdiv; 1.420 + totalQuadCount += 1 << subdiv; 1.421 + } 1.422 + } 1.423 + } 1.424 + break; 1.425 + } 1.426 + case SkPath::kCubic_Verb: 1.427 + m.mapPoints(devPts, pathPts, 4); 1.428 + bounds.setBounds(devPts, 4); 1.429 + bounds.outset(SK_Scalar1, SK_Scalar1); 1.430 + bounds.roundOut(&ibounds); 1.431 + if (SkIRect::Intersects(devClipBounds, ibounds)) { 1.432 + PREALLOC_PTARRAY(32) q; 1.433 + // we don't need a direction if we aren't constraining the subdivision 1.434 + static const SkPath::Direction kDummyDir = SkPath::kCCW_Direction; 1.435 + // We convert cubics to quadratics (for now). 1.436 + // In perspective have to do conversion in src space. 1.437 + if (persp) { 1.438 + SkScalar tolScale = 1.439 + GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m, 1.440 + path.getBounds()); 1.441 + GrPathUtils::convertCubicToQuads(pathPts, tolScale, false, kDummyDir, &q); 1.442 + } else { 1.443 + GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, false, kDummyDir, &q); 1.444 + } 1.445 + for (int i = 0; i < q.count(); i += 3) { 1.446 + SkPoint* qInDevSpace; 1.447 + // bounds has to be calculated in device space, but q is 1.448 + // in src space when there is perspective. 1.449 + if (persp) { 1.450 + m.mapPoints(devPts, &q[i], 3); 1.451 + bounds.setBounds(devPts, 3); 1.452 + qInDevSpace = devPts; 1.453 + } else { 1.454 + bounds.setBounds(&q[i], 3); 1.455 + qInDevSpace = &q[i]; 1.456 + } 1.457 + bounds.outset(SK_Scalar1, SK_Scalar1); 1.458 + bounds.roundOut(&ibounds); 1.459 + if (SkIRect::Intersects(devClipBounds, ibounds)) { 1.460 + int subdiv = num_quad_subdivs(qInDevSpace); 1.461 + SkASSERT(subdiv >= -1); 1.462 + if (-1 == subdiv) { 1.463 + SkPoint* pts = lines->push_back_n(4); 1.464 + // lines should always be in device coords 1.465 + pts[0] = qInDevSpace[0]; 1.466 + pts[1] = qInDevSpace[1]; 1.467 + pts[2] = qInDevSpace[1]; 1.468 + pts[3] = qInDevSpace[2]; 1.469 + } else { 1.470 + SkPoint* pts = quads->push_back_n(3); 1.471 + // q is already in src space when there is no 1.472 + // perspective and dev coords otherwise. 1.473 + pts[0] = q[0 + i]; 1.474 + pts[1] = q[1 + i]; 1.475 + pts[2] = q[2 + i]; 1.476 + quadSubdivCnts->push_back() = subdiv; 1.477 + totalQuadCount += 1 << subdiv; 1.478 + } 1.479 + } 1.480 + } 1.481 + } 1.482 + break; 1.483 + case SkPath::kClose_Verb: 1.484 + break; 1.485 + case SkPath::kDone_Verb: 1.486 + return totalQuadCount; 1.487 + } 1.488 + } 1.489 +} 1.490 + 1.491 +struct LineVertex { 1.492 + GrPoint fPos; 1.493 + GrColor fCoverage; 1.494 +}; 1.495 + 1.496 +struct BezierVertex { 1.497 + GrPoint fPos; 1.498 + union { 1.499 + struct { 1.500 + SkScalar fK; 1.501 + SkScalar fL; 1.502 + SkScalar fM; 1.503 + } fConic; 1.504 + GrVec fQuadCoord; 1.505 + struct { 1.506 + SkScalar fBogus[4]; 1.507 + }; 1.508 + }; 1.509 +}; 1.510 + 1.511 +GR_STATIC_ASSERT(sizeof(BezierVertex) == 3 * sizeof(GrPoint)); 1.512 + 1.513 +void intersect_lines(const SkPoint& ptA, const SkVector& normA, 1.514 + const SkPoint& ptB, const SkVector& normB, 1.515 + SkPoint* result) { 1.516 + 1.517 + SkScalar lineAW = -normA.dot(ptA); 1.518 + SkScalar lineBW = -normB.dot(ptB); 1.519 + 1.520 + SkScalar wInv = SkScalarMul(normA.fX, normB.fY) - 1.521 + SkScalarMul(normA.fY, normB.fX); 1.522 + wInv = SkScalarInvert(wInv); 1.523 + 1.524 + result->fX = SkScalarMul(normA.fY, lineBW) - SkScalarMul(lineAW, normB.fY); 1.525 + result->fX = SkScalarMul(result->fX, wInv); 1.526 + 1.527 + result->fY = SkScalarMul(lineAW, normB.fX) - SkScalarMul(normA.fX, lineBW); 1.528 + result->fY = SkScalarMul(result->fY, wInv); 1.529 +} 1.530 + 1.531 +void set_uv_quad(const SkPoint qpts[3], BezierVertex verts[kVertsPerQuad]) { 1.532 + // this should be in the src space, not dev coords, when we have perspective 1.533 + GrPathUtils::QuadUVMatrix DevToUV(qpts); 1.534 + DevToUV.apply<kVertsPerQuad, sizeof(BezierVertex), sizeof(GrPoint)>(verts); 1.535 +} 1.536 + 1.537 +void bloat_quad(const SkPoint qpts[3], const SkMatrix* toDevice, 1.538 + const SkMatrix* toSrc, BezierVertex verts[kVertsPerQuad], 1.539 + SkRect* devBounds) { 1.540 + SkASSERT(!toDevice == !toSrc); 1.541 + // original quad is specified by tri a,b,c 1.542 + SkPoint a = qpts[0]; 1.543 + SkPoint b = qpts[1]; 1.544 + SkPoint c = qpts[2]; 1.545 + 1.546 + if (toDevice) { 1.547 + toDevice->mapPoints(&a, 1); 1.548 + toDevice->mapPoints(&b, 1); 1.549 + toDevice->mapPoints(&c, 1); 1.550 + } 1.551 + // make a new poly where we replace a and c by a 1-pixel wide edges orthog 1.552 + // to edges ab and bc: 1.553 + // 1.554 + // before | after 1.555 + // | b0 1.556 + // b | 1.557 + // | 1.558 + // | a0 c0 1.559 + // a c | a1 c1 1.560 + // 1.561 + // edges a0->b0 and b0->c0 are parallel to original edges a->b and b->c, 1.562 + // respectively. 1.563 + BezierVertex& a0 = verts[0]; 1.564 + BezierVertex& a1 = verts[1]; 1.565 + BezierVertex& b0 = verts[2]; 1.566 + BezierVertex& c0 = verts[3]; 1.567 + BezierVertex& c1 = verts[4]; 1.568 + 1.569 + SkVector ab = b; 1.570 + ab -= a; 1.571 + SkVector ac = c; 1.572 + ac -= a; 1.573 + SkVector cb = b; 1.574 + cb -= c; 1.575 + 1.576 + // We should have already handled degenerates 1.577 + SkASSERT(ab.length() > 0 && cb.length() > 0); 1.578 + 1.579 + ab.normalize(); 1.580 + SkVector abN; 1.581 + abN.setOrthog(ab, SkVector::kLeft_Side); 1.582 + if (abN.dot(ac) > 0) { 1.583 + abN.negate(); 1.584 + } 1.585 + 1.586 + cb.normalize(); 1.587 + SkVector cbN; 1.588 + cbN.setOrthog(cb, SkVector::kLeft_Side); 1.589 + if (cbN.dot(ac) < 0) { 1.590 + cbN.negate(); 1.591 + } 1.592 + 1.593 + a0.fPos = a; 1.594 + a0.fPos += abN; 1.595 + a1.fPos = a; 1.596 + a1.fPos -= abN; 1.597 + 1.598 + c0.fPos = c; 1.599 + c0.fPos += cbN; 1.600 + c1.fPos = c; 1.601 + c1.fPos -= cbN; 1.602 + 1.603 + intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos); 1.604 + devBounds->growToInclude(&verts[0].fPos, sizeof(BezierVertex), kVertsPerQuad); 1.605 + 1.606 + if (toSrc) { 1.607 + toSrc->mapPointsWithStride(&verts[0].fPos, sizeof(BezierVertex), kVertsPerQuad); 1.608 + } 1.609 +} 1.610 + 1.611 +// Equations based off of Loop-Blinn Quadratic GPU Rendering 1.612 +// Input Parametric: 1.613 +// 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) 1.614 +// Output Implicit: 1.615 +// f(x, y, w) = f(P) = K^2 - LM 1.616 +// K = dot(k, P), L = dot(l, P), M = dot(m, P) 1.617 +// k, l, m are calculated in function GrPathUtils::getConicKLM 1.618 +void set_conic_coeffs(const SkPoint p[3], BezierVertex verts[kVertsPerQuad], 1.619 + const SkScalar weight) { 1.620 + SkScalar klm[9]; 1.621 + 1.622 + GrPathUtils::getConicKLM(p, weight, klm); 1.623 + 1.624 + for (int i = 0; i < kVertsPerQuad; ++i) { 1.625 + const SkPoint pnt = verts[i].fPos; 1.626 + verts[i].fConic.fK = pnt.fX * klm[0] + pnt.fY * klm[1] + klm[2]; 1.627 + verts[i].fConic.fL = pnt.fX * klm[3] + pnt.fY * klm[4] + klm[5]; 1.628 + verts[i].fConic.fM = pnt.fX * klm[6] + pnt.fY * klm[7] + klm[8]; 1.629 + } 1.630 +} 1.631 + 1.632 +void add_conics(const SkPoint p[3], 1.633 + const SkScalar weight, 1.634 + const SkMatrix* toDevice, 1.635 + const SkMatrix* toSrc, 1.636 + BezierVertex** vert, 1.637 + SkRect* devBounds) { 1.638 + bloat_quad(p, toDevice, toSrc, *vert, devBounds); 1.639 + set_conic_coeffs(p, *vert, weight); 1.640 + *vert += kVertsPerQuad; 1.641 +} 1.642 + 1.643 +void add_quads(const SkPoint p[3], 1.644 + int subdiv, 1.645 + const SkMatrix* toDevice, 1.646 + const SkMatrix* toSrc, 1.647 + BezierVertex** vert, 1.648 + SkRect* devBounds) { 1.649 + SkASSERT(subdiv >= 0); 1.650 + if (subdiv) { 1.651 + SkPoint newP[5]; 1.652 + SkChopQuadAtHalf(p, newP); 1.653 + add_quads(newP + 0, subdiv-1, toDevice, toSrc, vert, devBounds); 1.654 + add_quads(newP + 2, subdiv-1, toDevice, toSrc, vert, devBounds); 1.655 + } else { 1.656 + bloat_quad(p, toDevice, toSrc, *vert, devBounds); 1.657 + set_uv_quad(p, *vert); 1.658 + *vert += kVertsPerQuad; 1.659 + } 1.660 +} 1.661 + 1.662 +void add_line(const SkPoint p[2], 1.663 + const SkMatrix* toSrc, 1.664 + GrColor coverage, 1.665 + LineVertex** vert) { 1.666 + const SkPoint& a = p[0]; 1.667 + const SkPoint& b = p[1]; 1.668 + 1.669 + SkVector ortho, vec = b; 1.670 + vec -= a; 1.671 + 1.672 + if (vec.setLength(SK_ScalarHalf)) { 1.673 + // Create a vector orthogonal to 'vec' and of unit length 1.674 + ortho.fX = 2.0f * vec.fY; 1.675 + ortho.fY = -2.0f * vec.fX; 1.676 + 1.677 + (*vert)[0].fPos = a; 1.678 + (*vert)[0].fCoverage = coverage; 1.679 + (*vert)[1].fPos = b; 1.680 + (*vert)[1].fCoverage = coverage; 1.681 + (*vert)[2].fPos = a - vec + ortho; 1.682 + (*vert)[2].fCoverage = 0; 1.683 + (*vert)[3].fPos = b + vec + ortho; 1.684 + (*vert)[3].fCoverage = 0; 1.685 + (*vert)[4].fPos = a - vec - ortho; 1.686 + (*vert)[4].fCoverage = 0; 1.687 + (*vert)[5].fPos = b + vec - ortho; 1.688 + (*vert)[5].fCoverage = 0; 1.689 + 1.690 + if (NULL != toSrc) { 1.691 + toSrc->mapPointsWithStride(&(*vert)->fPos, 1.692 + sizeof(LineVertex), 1.693 + kVertsPerLineSeg); 1.694 + } 1.695 + } else { 1.696 + // just make it degenerate and likely offscreen 1.697 + for (int i = 0; i < kVertsPerLineSeg; ++i) { 1.698 + (*vert)[i].fPos.set(SK_ScalarMax, SK_ScalarMax); 1.699 + } 1.700 + } 1.701 + 1.702 + *vert += kVertsPerLineSeg; 1.703 +} 1.704 + 1.705 +} 1.706 + 1.707 +/////////////////////////////////////////////////////////////////////////////// 1.708 + 1.709 +namespace { 1.710 + 1.711 +// position + edge 1.712 +extern const GrVertexAttrib gHairlineBezierAttribs[] = { 1.713 + {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding}, 1.714 + {kVec4f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding} 1.715 +}; 1.716 + 1.717 +// position + coverage 1.718 +extern const GrVertexAttrib gHairlineLineAttribs[] = { 1.719 + {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding}, 1.720 + {kVec4ub_GrVertexAttribType, sizeof(GrPoint), kCoverage_GrVertexAttribBinding}, 1.721 +}; 1.722 + 1.723 +}; 1.724 + 1.725 +bool GrAAHairLinePathRenderer::createLineGeom(const SkPath& path, 1.726 + GrDrawTarget* target, 1.727 + const PtArray& lines, 1.728 + int lineCnt, 1.729 + GrDrawTarget::AutoReleaseGeometry* arg, 1.730 + SkRect* devBounds) { 1.731 + GrDrawState* drawState = target->drawState(); 1.732 + 1.733 + const SkMatrix& viewM = drawState->getViewMatrix(); 1.734 + 1.735 + int vertCnt = kVertsPerLineSeg * lineCnt; 1.736 + 1.737 + drawState->setVertexAttribs<gHairlineLineAttribs>(SK_ARRAY_COUNT(gHairlineLineAttribs)); 1.738 + SkASSERT(sizeof(LineVertex) == drawState->getVertexSize()); 1.739 + 1.740 + if (!arg->set(target, vertCnt, 0)) { 1.741 + return false; 1.742 + } 1.743 + 1.744 + LineVertex* verts = reinterpret_cast<LineVertex*>(arg->vertices()); 1.745 + 1.746 + const SkMatrix* toSrc = NULL; 1.747 + SkMatrix ivm; 1.748 + 1.749 + if (viewM.hasPerspective()) { 1.750 + if (viewM.invert(&ivm)) { 1.751 + toSrc = &ivm; 1.752 + } 1.753 + } 1.754 + devBounds->set(lines.begin(), lines.count()); 1.755 + for (int i = 0; i < lineCnt; ++i) { 1.756 + add_line(&lines[2*i], toSrc, drawState->getCoverageColor(), &verts); 1.757 + } 1.758 + // All the verts computed by add_line are within sqrt(1^2 + 0.5^2) of the end points. 1.759 + static const SkScalar kSqrtOfOneAndAQuarter = 1.118f; 1.760 + // Add a little extra to account for vector normalization precision. 1.761 + static const SkScalar kOutset = kSqrtOfOneAndAQuarter + SK_Scalar1 / 20; 1.762 + devBounds->outset(kOutset, kOutset); 1.763 + 1.764 + return true; 1.765 +} 1.766 + 1.767 +bool GrAAHairLinePathRenderer::createBezierGeom( 1.768 + const SkPath& path, 1.769 + GrDrawTarget* target, 1.770 + const PtArray& quads, 1.771 + int quadCnt, 1.772 + const PtArray& conics, 1.773 + int conicCnt, 1.774 + const IntArray& qSubdivs, 1.775 + const FloatArray& cWeights, 1.776 + GrDrawTarget::AutoReleaseGeometry* arg, 1.777 + SkRect* devBounds) { 1.778 + GrDrawState* drawState = target->drawState(); 1.779 + 1.780 + const SkMatrix& viewM = drawState->getViewMatrix(); 1.781 + 1.782 + int vertCnt = kVertsPerQuad * quadCnt + kVertsPerQuad * conicCnt; 1.783 + 1.784 + target->drawState()->setVertexAttribs<gHairlineBezierAttribs>(SK_ARRAY_COUNT(gHairlineBezierAttribs)); 1.785 + SkASSERT(sizeof(BezierVertex) == target->getDrawState().getVertexSize()); 1.786 + 1.787 + if (!arg->set(target, vertCnt, 0)) { 1.788 + return false; 1.789 + } 1.790 + 1.791 + BezierVertex* verts = reinterpret_cast<BezierVertex*>(arg->vertices()); 1.792 + 1.793 + const SkMatrix* toDevice = NULL; 1.794 + const SkMatrix* toSrc = NULL; 1.795 + SkMatrix ivm; 1.796 + 1.797 + if (viewM.hasPerspective()) { 1.798 + if (viewM.invert(&ivm)) { 1.799 + toDevice = &viewM; 1.800 + toSrc = &ivm; 1.801 + } 1.802 + } 1.803 + 1.804 + // Seed the dev bounds with some pts known to be inside. Each quad and conic grows the bounding 1.805 + // box to include its vertices. 1.806 + SkPoint seedPts[2]; 1.807 + if (quadCnt) { 1.808 + seedPts[0] = quads[0]; 1.809 + seedPts[1] = quads[2]; 1.810 + } else if (conicCnt) { 1.811 + seedPts[0] = conics[0]; 1.812 + seedPts[1] = conics[2]; 1.813 + } 1.814 + if (NULL != toDevice) { 1.815 + toDevice->mapPoints(seedPts, 2); 1.816 + } 1.817 + devBounds->set(seedPts[0], seedPts[1]); 1.818 + 1.819 + int unsubdivQuadCnt = quads.count() / 3; 1.820 + for (int i = 0; i < unsubdivQuadCnt; ++i) { 1.821 + SkASSERT(qSubdivs[i] >= 0); 1.822 + add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts, devBounds); 1.823 + } 1.824 + 1.825 + // Start Conics 1.826 + for (int i = 0; i < conicCnt; ++i) { 1.827 + add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &verts, devBounds); 1.828 + } 1.829 + return true; 1.830 +} 1.831 + 1.832 +bool GrAAHairLinePathRenderer::canDrawPath(const SkPath& path, 1.833 + const SkStrokeRec& stroke, 1.834 + const GrDrawTarget* target, 1.835 + bool antiAlias) const { 1.836 + if (!antiAlias) { 1.837 + return false; 1.838 + } 1.839 + 1.840 + if (!IsStrokeHairlineOrEquivalent(stroke, 1.841 + target->getDrawState().getViewMatrix(), 1.842 + NULL)) { 1.843 + return false; 1.844 + } 1.845 + 1.846 + if (SkPath::kLine_SegmentMask == path.getSegmentMasks() || 1.847 + target->caps()->shaderDerivativeSupport()) { 1.848 + return true; 1.849 + } 1.850 + return false; 1.851 +} 1.852 + 1.853 +template <class VertexType> 1.854 +bool check_bounds(GrDrawState* drawState, const SkRect& devBounds, void* vertices, int vCount) 1.855 +{ 1.856 + SkRect tolDevBounds = devBounds; 1.857 + // The bounds ought to be tight, but in perspective the below code runs the verts 1.858 + // through the view matrix to get back to dev coords, which can introduce imprecision. 1.859 + if (drawState->getViewMatrix().hasPerspective()) { 1.860 + tolDevBounds.outset(SK_Scalar1 / 1000, SK_Scalar1 / 1000); 1.861 + } else { 1.862 + // Non-persp matrices cause this path renderer to draw in device space. 1.863 + SkASSERT(drawState->getViewMatrix().isIdentity()); 1.864 + } 1.865 + SkRect actualBounds; 1.866 + 1.867 + VertexType* verts = reinterpret_cast<VertexType*>(vertices); 1.868 + bool first = true; 1.869 + for (int i = 0; i < vCount; ++i) { 1.870 + SkPoint pos = verts[i].fPos; 1.871 + // This is a hack to workaround the fact that we move some degenerate segments offscreen. 1.872 + if (SK_ScalarMax == pos.fX) { 1.873 + continue; 1.874 + } 1.875 + drawState->getViewMatrix().mapPoints(&pos, 1); 1.876 + if (first) { 1.877 + actualBounds.set(pos.fX, pos.fY, pos.fX, pos.fY); 1.878 + first = false; 1.879 + } else { 1.880 + actualBounds.growToInclude(pos.fX, pos.fY); 1.881 + } 1.882 + } 1.883 + if (!first) { 1.884 + return tolDevBounds.contains(actualBounds); 1.885 + } 1.886 + 1.887 + return true; 1.888 +} 1.889 + 1.890 +bool GrAAHairLinePathRenderer::onDrawPath(const SkPath& path, 1.891 + const SkStrokeRec& stroke, 1.892 + GrDrawTarget* target, 1.893 + bool antiAlias) { 1.894 + GrDrawState* drawState = target->drawState(); 1.895 + 1.896 + SkScalar hairlineCoverage; 1.897 + if (IsStrokeHairlineOrEquivalent(stroke, 1.898 + target->getDrawState().getViewMatrix(), 1.899 + &hairlineCoverage)) { 1.900 + uint8_t newCoverage = SkScalarRoundToInt(hairlineCoverage * 1.901 + target->getDrawState().getCoverage()); 1.902 + target->drawState()->setCoverage(newCoverage); 1.903 + } 1.904 + 1.905 + SkIRect devClipBounds; 1.906 + target->getClip()->getConservativeBounds(drawState->getRenderTarget(), &devClipBounds); 1.907 + 1.908 + int lineCnt; 1.909 + int quadCnt; 1.910 + int conicCnt; 1.911 + PREALLOC_PTARRAY(128) lines; 1.912 + PREALLOC_PTARRAY(128) quads; 1.913 + PREALLOC_PTARRAY(128) conics; 1.914 + IntArray qSubdivs; 1.915 + FloatArray cWeights; 1.916 + quadCnt = generate_lines_and_quads(path, drawState->getViewMatrix(), devClipBounds, 1.917 + &lines, &quads, &conics, &qSubdivs, &cWeights); 1.918 + lineCnt = lines.count() / 2; 1.919 + conicCnt = conics.count() / 3; 1.920 + 1.921 + // do lines first 1.922 + if (lineCnt) { 1.923 + GrDrawTarget::AutoReleaseGeometry arg; 1.924 + SkRect devBounds; 1.925 + 1.926 + if (!this->createLineGeom(path, 1.927 + target, 1.928 + lines, 1.929 + lineCnt, 1.930 + &arg, 1.931 + &devBounds)) { 1.932 + return false; 1.933 + } 1.934 + 1.935 + GrDrawTarget::AutoStateRestore asr; 1.936 + 1.937 + // createLineGeom transforms the geometry to device space when the matrix does not have 1.938 + // perspective. 1.939 + if (target->getDrawState().getViewMatrix().hasPerspective()) { 1.940 + asr.set(target, GrDrawTarget::kPreserve_ASRInit); 1.941 + } else if (!asr.setIdentity(target, GrDrawTarget::kPreserve_ASRInit)) { 1.942 + return false; 1.943 + } 1.944 + GrDrawState* drawState = target->drawState(); 1.945 + 1.946 + // Check devBounds 1.947 + SkASSERT(check_bounds<LineVertex>(drawState, devBounds, arg.vertices(), 1.948 + kVertsPerLineSeg * lineCnt)); 1.949 + 1.950 + { 1.951 + GrDrawState::AutoRestoreEffects are(drawState); 1.952 + target->setIndexSourceToBuffer(fLinesIndexBuffer); 1.953 + int lines = 0; 1.954 + while (lines < lineCnt) { 1.955 + int n = GrMin(lineCnt - lines, kNumLineSegsInIdxBuffer); 1.956 + target->drawIndexed(kTriangles_GrPrimitiveType, 1.957 + kVertsPerLineSeg*lines, // startV 1.958 + 0, // startI 1.959 + kVertsPerLineSeg*n, // vCount 1.960 + kIdxsPerLineSeg*n, // iCount 1.961 + &devBounds); 1.962 + lines += n; 1.963 + } 1.964 + } 1.965 + } 1.966 + 1.967 + // then quadratics/conics 1.968 + if (quadCnt || conicCnt) { 1.969 + GrDrawTarget::AutoReleaseGeometry arg; 1.970 + SkRect devBounds; 1.971 + 1.972 + if (!this->createBezierGeom(path, 1.973 + target, 1.974 + quads, 1.975 + quadCnt, 1.976 + conics, 1.977 + conicCnt, 1.978 + qSubdivs, 1.979 + cWeights, 1.980 + &arg, 1.981 + &devBounds)) { 1.982 + return false; 1.983 + } 1.984 + 1.985 + GrDrawTarget::AutoStateRestore asr; 1.986 + 1.987 + // createGeom transforms the geometry to device space when the matrix does not have 1.988 + // perspective. 1.989 + if (target->getDrawState().getViewMatrix().hasPerspective()) { 1.990 + asr.set(target, GrDrawTarget::kPreserve_ASRInit); 1.991 + } else if (!asr.setIdentity(target, GrDrawTarget::kPreserve_ASRInit)) { 1.992 + return false; 1.993 + } 1.994 + GrDrawState* drawState = target->drawState(); 1.995 + 1.996 + static const int kEdgeAttrIndex = 1; 1.997 + 1.998 + // Check devBounds 1.999 + SkASSERT(check_bounds<BezierVertex>(drawState, devBounds, arg.vertices(), 1.1000 + kVertsPerQuad * quadCnt + kVertsPerQuad * conicCnt)); 1.1001 + 1.1002 + if (quadCnt > 0) { 1.1003 + GrEffectRef* hairQuadEffect = GrQuadEffect::Create(kHairlineAA_GrEffectEdgeType, 1.1004 + *target->caps()); 1.1005 + SkASSERT(NULL != hairQuadEffect); 1.1006 + GrDrawState::AutoRestoreEffects are(drawState); 1.1007 + target->setIndexSourceToBuffer(fQuadsIndexBuffer); 1.1008 + drawState->addCoverageEffect(hairQuadEffect, kEdgeAttrIndex)->unref(); 1.1009 + int quads = 0; 1.1010 + while (quads < quadCnt) { 1.1011 + int n = GrMin(quadCnt - quads, kNumQuadsInIdxBuffer); 1.1012 + target->drawIndexed(kTriangles_GrPrimitiveType, 1.1013 + kVertsPerQuad*quads, // startV 1.1014 + 0, // startI 1.1015 + kVertsPerQuad*n, // vCount 1.1016 + kIdxsPerQuad*n, // iCount 1.1017 + &devBounds); 1.1018 + quads += n; 1.1019 + } 1.1020 + } 1.1021 + 1.1022 + if (conicCnt > 0) { 1.1023 + GrDrawState::AutoRestoreEffects are(drawState); 1.1024 + GrEffectRef* hairConicEffect = GrConicEffect::Create(kHairlineAA_GrEffectEdgeType, 1.1025 + *target->caps()); 1.1026 + SkASSERT(NULL != hairConicEffect); 1.1027 + drawState->addCoverageEffect(hairConicEffect, 1, 2)->unref(); 1.1028 + int conics = 0; 1.1029 + while (conics < conicCnt) { 1.1030 + int n = GrMin(conicCnt - conics, kNumQuadsInIdxBuffer); 1.1031 + target->drawIndexed(kTriangles_GrPrimitiveType, 1.1032 + kVertsPerQuad*(quadCnt + conics), // startV 1.1033 + 0, // startI 1.1034 + kVertsPerQuad*n, // vCount 1.1035 + kIdxsPerQuad*n, // iCount 1.1036 + &devBounds); 1.1037 + conics += n; 1.1038 + } 1.1039 + } 1.1040 + } 1.1041 + 1.1042 + target->resetIndexSource(); 1.1043 + 1.1044 + return true; 1.1045 +}