Index: src/gpu/GrAAHairLinePathRenderer.cpp |
diff --git a/src/gpu/GrAAHairLinePathRenderer.cpp b/src/gpu/GrAAHairLinePathRenderer.cpp |
index 51bff1efc361dfdb33d7509c94b48198c88bbb7a..332c6bb5a14055f2728b7d4fe5d77b180ec93368 100644 |
--- a/src/gpu/GrAAHairLinePathRenderer.cpp |
+++ b/src/gpu/GrAAHairLinePathRenderer.cpp |
@@ -26,7 +26,7 @@ |
namespace { |
// quadratics are rendered as 5-sided polys in order to bound the |
// AA stroke around the center-curve. See comments in push_quad_index_buffer and |
-// bloat_quad. |
+// bloat_quad. Quadratics and conics share an index buffer |
static const int kVertsPerQuad = 5; |
static const int kIdxsPerQuad = 9; |
@@ -117,6 +117,7 @@ namespace { |
typedef SkTArray<SkPoint, true> PtArray; |
#define PREALLOC_PTARRAY(N) SkSTArray<(N),SkPoint, true> |
typedef SkTArray<int, true> IntArray; |
+typedef SkTArray<float, true> FloatArray; |
// Takes 178th time of logf on Z600 / VC2010 |
int get_float_exp(float x) { |
@@ -142,6 +143,52 @@ int get_float_exp(float x) { |
return (((*iptr) & 0x7f800000) >> 23) - 127; |
} |
+// Uses the max curvature function for quads to estimate |
+// where to chop the conic. If the max curvature is not |
+// found along the curve segment it will return 1 and |
+// dst[0] is the orginal conic. If it returns 2 the dst[0] |
+// and dst[1] are the two new conics. |
+int chop_conic(const SkPoint src[3], SkConic dst[2], const SkScalar weight) { |
+ SkScalar t = SkFindQuadMaxCurvature(src); |
+ if (t == 0) { |
+ if (dst) { |
+ dst[0].set(src, weight); |
+ } |
+ return 1; |
+ } else { |
+ if (dst) { |
+ SkConic conic; |
+ conic.set(src, weight); |
+ conic.chopAt(t, dst); |
+ } |
+ return 2; |
+ } |
+} |
+ |
+// returns 0 if quad/conic is degen or close to it |
+// in this case approx the path with lines |
+// otherwise returns 1 |
+int is_degen_quad_or_conic(const SkPoint p[3]) { |
+ static const SkScalar gDegenerateToLineTol = SK_Scalar1; |
+ static const SkScalar gDegenerateToLineTolSqd = |
+ SkScalarMul(gDegenerateToLineTol, gDegenerateToLineTol); |
+ |
+ if (p[0].distanceToSqd(p[1]) < gDegenerateToLineTolSqd || |
+ p[1].distanceToSqd(p[2]) < gDegenerateToLineTolSqd) { |
+ return 1; |
+ } |
+ |
+ SkScalar dsqd = p[1].distanceToLineBetweenSqd(p[0], p[2]); |
+ if (dsqd < gDegenerateToLineTolSqd) { |
+ return 1; |
+ } |
+ |
+ if (p[2].distanceToLineBetweenSqd(p[1], p[0]) < gDegenerateToLineTolSqd) { |
+ return 1; |
+ } |
+ return 0; |
+} |
+ |
// we subdivide the quads to avoid huge overfill |
// if it returns -1 then should be drawn as lines |
int num_quad_subdivs(const SkPoint p[3]) { |
@@ -207,7 +254,9 @@ int generate_lines_and_quads(const SkPath& path, |
const GrIRect& devClipBounds, |
PtArray* lines, |
PtArray* quads, |
- IntArray* quadSubdivCnts) { |
+ PtArray* conics, |
+ IntArray* quadSubdivCnts, |
+ FloatArray* conicWeights) { |
SkPath::Iter iter(path, false); |
int totalQuadCount = 0; |
@@ -221,10 +270,36 @@ int generate_lines_and_quads(const SkPath& path, |
GrPoint devPts[4]; |
SkPath::Verb verb = iter.next(pathPts); |
switch (verb) { |
- case SkPath::kConic_Verb: |
- SkASSERT(0); |
+ case SkPath::kConic_Verb: { |
+ SkConic dst[2]; |
+ int conicCnt = chop_conic(pathPts, dst, iter.conicWeight()); |
+ for (int i = 0; i < conicCnt; ++i) { |
+ SkPoint* chopPnts = dst[i].fPts; |
+ m.mapPoints(devPts, chopPnts, 3); |
+ bounds.setBounds(devPts, 3); |
+ bounds.outset(SK_Scalar1, SK_Scalar1); |
+ bounds.roundOut(&ibounds); |
+ if (SkIRect::Intersects(devClipBounds, ibounds)) { |
+ if (is_degen_quad_or_conic(devPts)) { |
+ SkPoint* pts = lines->push_back_n(4); |
+ pts[0] = devPts[0]; |
+ pts[1] = devPts[1]; |
+ pts[2] = devPts[1]; |
+ pts[3] = devPts[2]; |
+ } else { |
+ // when in perspective keep conics in src space |
+ SkPoint* cPts = persp ? chopPnts : devPts; |
+ SkPoint* pts = conics->push_back_n(3); |
+ pts[0] = cPts[0]; |
+ pts[1] = cPts[1]; |
+ pts[2] = cPts[2]; |
+ conicWeights->push_back() = dst[i].fW; |
+ } |
+ } |
+ } |
break; |
- case SkPath::kMove_Verb: |
+ } |
+ case SkPath::kMove_Verb: |
break; |
case SkPath::kLine_Verb: |
m.mapPoints(devPts, pathPts, 2); |
@@ -348,13 +423,22 @@ struct Vertex { |
SkScalar fB; |
SkScalar fC; |
} fLine; |
+ struct { |
+ SkScalar fA; |
+ SkScalar fB; |
+ SkScalar fC; |
+ SkScalar fD; |
+ SkScalar fE; |
+ SkScalar fF; |
+ } fConic; |
GrVec fQuadCoord; |
struct { |
- SkScalar fBogus[4]; |
+ SkScalar fBogus[6]; |
}; |
}; |
}; |
-GR_STATIC_ASSERT(sizeof(Vertex) == 3 * sizeof(GrPoint)); |
+ |
+GR_STATIC_ASSERT(sizeof(Vertex) == 4 * sizeof(GrPoint)); |
void intersect_lines(const SkPoint& ptA, const SkVector& normA, |
const SkPoint& ptB, const SkVector& normB, |
@@ -364,7 +448,7 @@ void intersect_lines(const SkPoint& ptA, const SkVector& normA, |
SkScalar lineBW = -normB.dot(ptB); |
SkScalar wInv = SkScalarMul(normA.fX, normB.fY) - |
- SkScalarMul(normA.fY, normB.fX); |
+ SkScalarMul(normA.fY, normB.fX); |
wInv = SkScalarInvert(wInv); |
result->fX = SkScalarMul(normA.fY, lineBW) - SkScalarMul(lineAW, normB.fY); |
@@ -454,6 +538,57 @@ void bloat_quad(const SkPoint qpts[3], const SkMatrix* toDevice, |
DevToUV.apply<kVertsPerQuad, sizeof(Vertex), sizeof(GrPoint)>(verts); |
} |
+ |
+// Input Parametric: |
+// 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) |
+// Output Implicit: |
+// Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0 |
+// A = 4w^2*(y0-y1)(y1-y2)-(y0-y2)^2 |
+// B = 4w^2*((x0-x1)(y2-y1)+(x1-x2)(y1-y0)) + 2(x0-x2)(y0-y2) |
+// C = 4w^2(x0-x1)(x1-x2) - (x0-x2)^2 |
+// D = 4w^2((x0y1-x1y0)(y1-y2)+(x1y2-x2y1)(y0-y1)) + 2(y2-y0)(x0y2-x2y0) |
+// E = 4w^2((y0x1-y1x0)(x1-x2)+(y1x2-y2x1)(x0-x1)) + 2(x2-x0)(y0x2-y2x0) |
+// F = 4w^2(x1y2-x2y1)(x0y1-x1y0) - (x2y0-x0y2)^2 |
+ |
+void set_conic_coeffs(const SkPoint p[3], Vertex verts[kVertsPerQuad], const float weight) { |
+ const float ww4 = 4 * weight * weight; |
+ const float x0Mx1 = p[0].fX - p[1].fX; |
+ const float x1Mx2 = p[1].fX - p[2].fX; |
+ const float x0Mx2 = p[0].fX - p[2].fX; |
+ const float y0My1 = p[0].fY - p[1].fY; |
+ const float y1My2 = p[1].fY - p[2].fY; |
+ const float y0My2 = p[0].fY - p[2].fY; |
+ const float x0y1Mx1y0 = p[0].fX*p[1].fY - p[1].fX*p[0].fY; |
+ const float x1y2Mx2y1 = p[1].fX*p[2].fY - p[2].fX*p[1].fY; |
+ const float x0y2Mx2y0 = p[0].fX*p[2].fY - p[2].fX*p[0].fY; |
+ const float a = ww4 * y0My1 * y1My2 - y0My2 * y0My2; |
+ const float b = -ww4 * (x0Mx1 * y1My2 + x1Mx2 * y0My1) + 2 * x0Mx2 * y0My2; |
+ const float c = ww4 * x0Mx1 * x1Mx2 - x0Mx2 * x0Mx2; |
+ const float d = ww4 * (x0y1Mx1y0 * y1My2 + x1y2Mx2y1 * y0My1) - 2 * y0My2 * x0y2Mx2y0; |
+ const float e = -ww4 * (x0y1Mx1y0 * x1Mx2 + x1y2Mx2y1 * x0Mx1) + 2 * x0Mx2 * x0y2Mx2y0; |
+ const float f = ww4 * x1y2Mx2y1 * x0y1Mx1y0 - x0y2Mx2y0 * x0y2Mx2y0; |
+ |
+ for (int i = 0; i < kVertsPerQuad; ++i) { |
+ verts[i].fConic.fA = a/f; |
+ verts[i].fConic.fB = b/f; |
+ verts[i].fConic.fC = c/f; |
+ verts[i].fConic.fD = d/f; |
+ verts[i].fConic.fE = e/f; |
+ verts[i].fConic.fF = f/f; |
+ } |
+} |
+ |
+void add_conics(const SkPoint p[3], |
+ float weight, |
+ const SkMatrix* toDevice, |
+ const SkMatrix* toSrc, |
+ Vertex** vert, |
+ SkRect* devBounds) { |
+ bloat_quad(p, toDevice, toSrc, *vert, devBounds); |
+ set_conic_coeffs(p, *vert, weight); |
+ *vert += kVertsPerQuad; |
+} |
+ |
void add_quads(const SkPoint p[3], |
int subdiv, |
const SkMatrix* toDevice, |
@@ -515,6 +650,119 @@ void add_line(const SkPoint p[2], |
} |
+/** |
+ * The output of this effect is a hairline edge for conics. |
+ * Conics specified by implicit equation Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0. |
+ * A, B, C, D are the first vec4 of vertex attributes and |
+ * E and F are the vec2 attached to 2nd vertex attrribute. |
+ * Coverage is max(0, 1-distance). |
+ */ |
+class HairConicEdgeEffect : public GrEffect { |
+public: |
+ static GrEffectRef* Create() { |
+ GR_CREATE_STATIC_EFFECT(gHairConicEdgeEffect, HairConicEdgeEffect, ()); |
+ gHairConicEdgeEffect->ref(); |
+ return gHairConicEdgeEffect; |
+ } |
+ |
+ virtual ~HairConicEdgeEffect() {} |
+ |
+ static const char* Name() { return "HairConicEdge"; } |
+ |
+ virtual void getConstantColorComponents(GrColor* color, |
+ uint32_t* validFlags) const SK_OVERRIDE { |
+ *validFlags = 0; |
+ } |
+ |
+ virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE { |
+ return GrTBackendEffectFactory<HairConicEdgeEffect>::getInstance(); |
+ } |
+ |
+ class GLEffect : public GrGLEffect { |
+ public: |
+ GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&) |
+ : INHERITED (factory) {} |
+ |
+ virtual void emitCode(GrGLShaderBuilder* builder, |
+ const GrDrawEffect& drawEffect, |
+ EffectKey key, |
+ const char* outputColor, |
+ const char* inputColor, |
+ const TextureSamplerArray& samplers) SK_OVERRIDE { |
+ const char *vsCoeffABCDName, *fsCoeffABCDName; |
+ const char *vsCoeffEFName, *fsCoeffEFName; |
+ |
+ SkAssertResult(builder->enableFeature( |
+ GrGLShaderBuilder::kStandardDerivatives_GLSLFeature)); |
+ builder->addVarying(kVec4f_GrSLType, "ConicCoeffsABCD", |
+ &vsCoeffABCDName, &fsCoeffABCDName); |
+ const SkString* attr0Name = |
+ builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[0]); |
+ builder->vsCodeAppendf("\t%s = %s;\n", vsCoeffABCDName, attr0Name->c_str()); |
+ |
+ builder->addVarying(kVec2f_GrSLType, "ConicCoeffsEF", |
+ &vsCoeffEFName, &fsCoeffEFName); |
+ const SkString* attr1Name = |
+ builder->getEffectAttributeName(drawEffect.getVertexAttribIndices()[1]); |
+ builder->vsCodeAppendf("\t%s = %s;\n", vsCoeffEFName, attr1Name->c_str()); |
+ |
+ // Based on Gustavson 2006: "Beyond the Pixel: towards infinite resolution textures" |
+ builder->fsCodeAppendf("\t\tfloat edgeAlpha;\n"); |
+ |
+ builder->fsCodeAppendf("\t\tvec3 uv1 = vec3(%s.xy, 1);\n", builder->fragmentPosition()); |
+ builder->fsCodeAppend("\t\tvec3 u2uvv2 = uv1.xxy * uv1.xyy;\n"); |
+ builder->fsCodeAppendf("\t\tvec3 ABC = %s.xyz;\n", fsCoeffABCDName); |
+ builder->fsCodeAppendf("\t\tvec3 DEF = vec3(%s.w, %s.xy);\n", |
+ fsCoeffABCDName, fsCoeffEFName); |
+ |
+ builder->fsCodeAppend("\t\tfloat dfdx = dot(uv1,vec3(2.0*ABC.x,ABC.y,DEF.x));\n"); |
+ builder->fsCodeAppend("\t\tfloat dfdy = dot(uv1,vec3(ABC.y, 2.0*ABC.z,DEF.y));\n"); |
+ builder->fsCodeAppend("\t\tfloat gF = dfdx*dfdx + dfdy*dfdy;\n"); |
+ builder->fsCodeAppend("\t\tedgeAlpha = dot(ABC,u2uvv2) + dot(DEF,uv1);\n"); |
+ builder->fsCodeAppend("\t\tedgeAlpha = sqrt(edgeAlpha*edgeAlpha / gF);\n"); |
+ builder->fsCodeAppend("\t\tedgeAlpha = max((1.0 - edgeAlpha), 0.0);\n"); |
+ // Add line below for smooth cubic ramp |
+ // builder->fsCodeAppend("\t\tedgeAlpha = edgeAlpha*edgeAlpha*(3.0-2.0*edgeAlpha);\n"); |
+ |
+ SkString modulate; |
+ GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha"); |
+ builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str()); |
+ } |
+ |
+ static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrGLCaps&) { |
+ return 0x0; |
+ } |
+ |
+ virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE {} |
+ |
+ private: |
+ typedef GrGLEffect INHERITED; |
+ }; |
+ |
+private: |
+ HairConicEdgeEffect() { |
+ this->addVertexAttrib(kVec4f_GrSLType); |
+ this->addVertexAttrib(kVec2f_GrSLType); |
+ this->setWillReadFragmentPosition(); |
+ } |
+ |
+ virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE { |
+ return true; |
+ } |
+ |
+ GR_DECLARE_EFFECT_TEST; |
+ |
+ typedef GrEffect INHERITED; |
+}; |
+ |
+GR_DEFINE_EFFECT_TEST(HairConicEdgeEffect); |
+ |
+GrEffectRef* HairConicEdgeEffect::TestCreate(SkMWCRandom* random, |
+ GrContext*, |
+ const GrDrawTargetCaps& caps, |
+ GrTexture*[]) { |
+ return HairConicEdgeEffect::Create(); |
+} |
/////////////////////////////////////////////////////////////////////////////// |
/** |
@@ -563,7 +811,7 @@ public: |
builder->fsCodeAppendf("\t\tfloat edgeAlpha;\n"); |
SkAssertResult(builder->enableFeature( |
- GrGLShaderBuilder::kStandardDerivatives_GLSLFeature)); |
+ GrGLShaderBuilder::kStandardDerivatives_GLSLFeature)); |
builder->addVarying(kVec4f_GrSLType, "HairQuadEdge", &vsName, &fsName); |
builder->fsCodeAppendf("\t\tvec2 duvdx = dFdx(%s.xy);\n", fsName); |
@@ -614,7 +862,8 @@ GrEffectRef* HairQuadEdgeEffect::TestCreate(SkMWCRandom* random, |
const GrDrawTargetCaps& caps, |
GrTexture*[]) { |
// Doesn't work without derivative instructions. |
- return caps.shaderDerivativeSupport() ? HairQuadEdgeEffect::Create() : NULL;} |
+ return caps.shaderDerivativeSupport() ? HairQuadEdgeEffect::Create() : NULL; |
+} |
/////////////////////////////////////////////////////////////////////////////// |
@@ -717,6 +966,13 @@ extern const GrVertexAttrib gHairlineAttribs[] = { |
{kVec4f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding} |
}; |
+// Conic |
+// position + ABCD + EF |
+extern const GrVertexAttrib gConicVertexAttribs[] = { |
+ { kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding }, |
+ { kVec4f_GrVertexAttribType, sizeof(GrPoint), kEffect_GrVertexAttribBinding }, |
+ { kVec2f_GrVertexAttribType, 3*sizeof(GrPoint), kEffect_GrVertexAttribBinding } |
+}; |
}; |
bool GrAAHairLinePathRenderer::createGeom( |
@@ -724,6 +980,7 @@ bool GrAAHairLinePathRenderer::createGeom( |
GrDrawTarget* target, |
int* lineCnt, |
int* quadCnt, |
+ int* conicCnt, |
GrDrawTarget::AutoReleaseGeometry* arg, |
SkRect* devBounds) { |
GrDrawState* drawState = target->drawState(); |
@@ -743,14 +1000,18 @@ bool GrAAHairLinePathRenderer::createGeom( |
PREALLOC_PTARRAY(128) lines; |
PREALLOC_PTARRAY(128) quads; |
+ PREALLOC_PTARRAY(128) conics; |
IntArray qSubdivs; |
+ FloatArray cWeights; |
*quadCnt = generate_lines_and_quads(path, viewM, devClipBounds, |
- &lines, &quads, &qSubdivs); |
+ &lines, &quads, &conics, &qSubdivs, &cWeights); |
*lineCnt = lines.count() / 2; |
- int vertCnt = kVertsPerLineSeg * *lineCnt + kVertsPerQuad * *quadCnt; |
+ *conicCnt = conics.count() / 3; |
+ int vertCnt = kVertsPerLineSeg * *lineCnt + kVertsPerQuad * *quadCnt + |
+ kVertsPerQuad * *conicCnt; |
- target->drawState()->setVertexAttribs<gHairlineAttribs>(SK_ARRAY_COUNT(gHairlineAttribs)); |
+ target->drawState()->setVertexAttribs<gConicVertexAttribs>(SK_ARRAY_COUNT(gConicVertexAttribs)); |
GrAssert(sizeof(Vertex) == target->getDrawState().getVertexSize()); |
if (!arg->set(target, vertCnt, 0)) { |
@@ -780,6 +1041,10 @@ bool GrAAHairLinePathRenderer::createGeom( |
add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts, devBounds); |
} |
+ // Start Conics |
+ for (int i = 0; i < *conicCnt; ++i) { |
+ add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &verts, devBounds); |
+ } |
return true; |
} |
@@ -807,6 +1072,7 @@ bool GrAAHairLinePathRenderer::onDrawPath(const SkPath& path, |
int lineCnt; |
int quadCnt; |
+ int conicCnt; |
GrDrawTarget::AutoReleaseGeometry arg; |
SkRect devBounds; |
@@ -814,6 +1080,7 @@ bool GrAAHairLinePathRenderer::onDrawPath(const SkPath& path, |
target, |
&lineCnt, |
&quadCnt, |
+ &conicCnt, |
&arg, |
&devBounds)) { |
return false; |
@@ -843,6 +1110,7 @@ bool GrAAHairLinePathRenderer::onDrawPath(const SkPath& path, |
GrEffectRef* hairLineEffect = HairLineEdgeEffect::Create(); |
GrEffectRef* hairQuadEffect = HairQuadEdgeEffect::Create(); |
+ GrEffectRef* hairConicEffect = HairConicEdgeEffect::Create(); |
// Check devBounds |
#if GR_DEBUG |
@@ -850,7 +1118,7 @@ bool GrAAHairLinePathRenderer::onDrawPath(const SkPath& path, |
tolDevBounds.outset(SK_Scalar1 / 10000, SK_Scalar1 / 10000); |
SkRect actualBounds; |
Vertex* verts = reinterpret_cast<Vertex*>(arg.vertices()); |
- int vCount = kVertsPerLineSeg * lineCnt + kVertsPerQuad * quadCnt; |
+ int vCount = kVertsPerLineSeg * lineCnt + kVertsPerQuad * quadCnt + kVertsPerQuad * conicCnt; |
bool first = true; |
for (int i = 0; i < vCount; ++i) { |
SkPoint pos = verts[i].fPos; |
@@ -889,20 +1157,41 @@ bool GrAAHairLinePathRenderer::onDrawPath(const SkPath& path, |
} |
} |
- target->setIndexSourceToBuffer(fQuadsIndexBuffer); |
- int quads = 0; |
- drawState->addCoverageEffect(hairQuadEffect, kEdgeAttrIndex)->unref(); |
- while (quads < quadCnt) { |
- int n = GrMin(quadCnt - quads, kNumQuadsInIdxBuffer); |
- target->drawIndexed(kTriangles_GrPrimitiveType, |
- 4 * lineCnt + kVertsPerQuad*quads, // startV |
- 0, // startI |
- kVertsPerQuad*n, // vCount |
- kIdxsPerQuad*n, // iCount |
- &devBounds); |
- quads += n; |
+ { |
+ GrDrawState::AutoRestoreEffects are(drawState); |
+ target->setIndexSourceToBuffer(fQuadsIndexBuffer); |
+ int quads = 0; |
+ drawState->addCoverageEffect(hairQuadEffect, kEdgeAttrIndex)->unref(); |
+ while (quads < quadCnt) { |
+ int n = GrMin(quadCnt - quads, kNumQuadsInIdxBuffer); |
+ target->drawIndexed(kTriangles_GrPrimitiveType, |
+ kVertsPerLineSeg * lineCnt + kVertsPerQuad*quads, // startV |
+ 0, // startI |
+ kVertsPerQuad*n, // vCount |
+ kIdxsPerQuad*n, // iCount |
+ &devBounds); |
+ quads += n; |
+ } |
+ } |
+ |
+ { |
+ GrDrawState::AutoRestoreEffects are(drawState); |
+ int conics = 0; |
+ drawState->addCoverageEffect(hairConicEffect, 1, 2)->unref(); |
+ while (conics < conicCnt) { |
+ int n = GrMin(conicCnt - conics, kNumQuadsInIdxBuffer); |
+ target->drawIndexed(kTriangles_GrPrimitiveType, |
+ kVertsPerLineSeg*lineCnt + |
+ kVertsPerQuad*(quadCnt + conics), // startV |
+ 0, // startI |
+ kVertsPerQuad*n, // vCount |
+ kIdxsPerQuad*n, // iCount |
+ &devBounds); |
+ conics += n; |
+ } |
} |
target->resetIndexSource(); |
return true; |
} |
+ |