OLD | NEW |
---|---|
1 /* | 1 /* |
2 * Copyright 2013 Google Inc. | 2 * Copyright 2013 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "GrOvalRenderer.h" | 8 #include "GrOvalRenderer.h" |
9 | 9 |
10 #include "GrEffect.h" | 10 #include "GrEffect.h" |
(...skipping 14 matching lines...) Expand all Loading... | |
25 | 25 |
26 struct CircleVertex { | 26 struct CircleVertex { |
27 GrPoint fPos; | 27 GrPoint fPos; |
28 GrPoint fOffset; | 28 GrPoint fOffset; |
29 SkScalar fOuterRadius; | 29 SkScalar fOuterRadius; |
30 SkScalar fInnerRadius; | 30 SkScalar fInnerRadius; |
31 }; | 31 }; |
32 | 32 |
33 struct EllipseVertex { | 33 struct EllipseVertex { |
34 GrPoint fPos; | 34 GrPoint fPos; |
35 SkScalar fOuterXRadius; | |
36 SkScalar fInnerXRadius; | |
37 GrPoint fOuterOffset; | |
38 GrPoint fInnerOffset; | |
39 }; | |
40 | |
41 struct RRectVertex { | |
42 GrPoint fPos; | |
43 GrPoint fOffset; | 35 GrPoint fOffset; |
44 GrPoint fOuterRadii; | 36 GrPoint fOuterRadii; |
45 GrPoint fInnerRadii; | 37 GrPoint fInnerRadii; |
46 }; | 38 }; |
47 | 39 |
48 inline bool circle_stays_circle(const SkMatrix& m) { | 40 inline bool circle_stays_circle(const SkMatrix& m) { |
49 return m.isSimilarity(); | 41 return m.isSimilarity(); |
50 } | 42 } |
51 | 43 |
52 } | 44 } |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
156 GrContext* context, | 148 GrContext* context, |
157 const GrDrawTargetCaps&, | 149 const GrDrawTargetCaps&, |
158 GrTexture* textures[]) { | 150 GrTexture* textures[]) { |
159 return CircleEdgeEffect::Create(random->nextBool()); | 151 return CircleEdgeEffect::Create(random->nextBool()); |
160 } | 152 } |
161 | 153 |
162 /////////////////////////////////////////////////////////////////////////////// | 154 /////////////////////////////////////////////////////////////////////////////// |
163 | 155 |
164 /** | 156 /** |
165 * The output of this effect is a modulation of the input color and coverage for an axis-aligned | 157 * The output of this effect is a modulation of the input color and coverage for an axis-aligned |
166 * ellipse, specified as outer and inner radii, and outer and inner offsets fro m center. | 158 * ellipse, specified as a 2D offset from center, and the reciprocals of the out er and inner radii, |
159 * in both x and y directions. | |
160 * | |
161 * We are using an implicit function of x^2/a^2 + y^2/b^2 - 1 = 0. | |
167 */ | 162 */ |
168 | 163 |
169 class EllipseEdgeEffect : public GrEffect { | 164 class EllipseEdgeEffect : public GrEffect { |
170 public: | 165 public: |
171 static GrEffectRef* Create(bool stroke) { | 166 static GrEffectRef* Create(bool stroke) { |
172 GR_CREATE_STATIC_EFFECT(gEllipseStrokeEdge, EllipseEdgeEffect, (true)); | 167 GR_CREATE_STATIC_EFFECT(gEllipseStrokeEdge, EllipseEdgeEffect, (true)); |
173 GR_CREATE_STATIC_EFFECT(gEllipseFillEdge, EllipseEdgeEffect, (false)); | 168 GR_CREATE_STATIC_EFFECT(gEllipseFillEdge, EllipseEdgeEffect, (false)); |
174 | 169 |
175 if (stroke) { | 170 if (stroke) { |
176 gEllipseStrokeEdge->ref(); | 171 gEllipseStrokeEdge->ref(); |
(...skipping 25 matching lines...) Expand all Loading... | |
202 : INHERITED (factory) {} | 197 : INHERITED (factory) {} |
203 | 198 |
204 virtual void emitCode(GrGLShaderBuilder* builder, | 199 virtual void emitCode(GrGLShaderBuilder* builder, |
205 const GrDrawEffect& drawEffect, | 200 const GrDrawEffect& drawEffect, |
206 EffectKey key, | 201 EffectKey key, |
207 const char* outputColor, | 202 const char* outputColor, |
208 const char* inputColor, | 203 const char* inputColor, |
209 const TextureSamplerArray& samplers) SK_OVERRIDE { | 204 const TextureSamplerArray& samplers) SK_OVERRIDE { |
210 const EllipseEdgeEffect& ellipseEffect = drawEffect.castEffect<Ellip seEdgeEffect>(); | 205 const EllipseEdgeEffect& ellipseEffect = drawEffect.castEffect<Ellip seEdgeEffect>(); |
211 | 206 |
207 const char *vsOffsetName, *fsOffsetName; | |
212 const char *vsRadiiName, *fsRadiiName; | 208 const char *vsRadiiName, *fsRadiiName; |
213 const char *vsOffsetsName, *fsOffsetsName; | |
214 | 209 |
215 builder->addVarying(kVec2f_GrSLType, "EllipseRadii", &vsRadiiName, & fsRadiiName); | 210 builder->addVarying(kVec2f_GrSLType, "EllipseOffsets", &vsOffsetName , &fsOffsetName); |
216 const SkString* attr0Name = | 211 const SkString* attr0Name = |
217 builder->getEffectAttributeName(drawEffect.getVertexAttribIndice s()[0]); | 212 builder->getEffectAttributeName(drawEffect.getVertexAttribIndice s()[0]); |
218 builder->vsCodeAppendf("\t%s = %s;\n", vsRadiiName, attr0Name->c_str ()); | 213 builder->vsCodeAppendf("\t%s = %s;\n", vsOffsetName, attr0Name->c_st r()); |
219 | 214 |
220 builder->addVarying(kVec4f_GrSLType, "EllipseOffsets", &vsOffsetsNam e, &fsOffsetsName); | 215 builder->addVarying(kVec4f_GrSLType, "EllipseRadii", &vsRadiiName, & fsRadiiName); |
221 const SkString* attr1Name = | 216 const SkString* attr1Name = |
222 builder->getEffectAttributeName(drawEffect.getVertexAttribIndice s()[1]); | 217 builder->getEffectAttributeName(drawEffect.getVertexAttribIndice s()[1]); |
223 builder->vsCodeAppendf("\t%s = %s;\n", vsOffsetsName, attr1Name->c_s tr()); | 218 builder->vsCodeAppendf("\t%s = %s;\n", vsRadiiName, attr1Name->c_str ()); |
224 | 219 |
225 // get length of offset | 220 // for outer curve |
226 builder->fsCodeAppendf("\tfloat dOuter = length(%s.xy);\n", fsOffset sName); | 221 builder->fsCodeAppendf("\tvec2 scaledOffset = %s*%s.xy;\n", fsOffset Name, fsRadiiName); |
227 // compare outer lengths against xOuterRadius | 222 builder->fsCodeAppend("\tfloat test = dot(scaledOffset, scaledOffset ) - 1.0;\n"); |
228 builder->fsCodeAppendf("\tfloat edgeAlpha = clamp(%s.x-dOuter, 0.0, 1.0);\n", | 223 builder->fsCodeAppendf("\tvec2 grad = 2.0*scaledOffset*%s.xy;\n", fs RadiiName); |
229 fsRadiiName); | 224 builder->fsCodeAppend("\tfloat invlen = inversesqrt(dot(grad, grad)) ;\n"); |
225 builder->fsCodeAppend("\tfloat edgeAlpha = clamp(0.5-test*invlen, 0. 0, 1.0);\n"); | |
230 | 226 |
227 // for inner curve | |
231 if (ellipseEffect.isStroked()) { | 228 if (ellipseEffect.isStroked()) { |
232 builder->fsCodeAppendf("\tfloat dInner = length(%s.zw);\n", fsOf fsetsName); | 229 builder->fsCodeAppendf("\tscaledOffset = %s*%s.zw;\n", fsOffsetN ame, fsRadiiName); |
233 | 230 builder->fsCodeAppend("\ttest = dot(scaledOffset, scaledOffset) - 1.0;\n"); |
234 // compare inner lengths against xInnerRadius | 231 builder->fsCodeAppendf("\tgrad = 2.0*scaledOffset*%s.zw;\n", fsR adiiName); |
235 builder->fsCodeAppendf("\tfloat innerAlpha = clamp(dInner-%s.y, 0.0, 1.0);\n", | 232 builder->fsCodeAppend("\tinvlen = inversesqrt(dot(grad, grad));\ n"); |
236 fsRadiiName); | 233 builder->fsCodeAppend("\tedgeAlpha *= clamp(0.5+test*invlen, 0.0 , 1.0);\n"); |
237 builder->fsCodeAppend("\tedgeAlpha *= innerAlpha;\n"); | |
238 } | 234 } |
239 | 235 |
240 SkString modulate; | 236 SkString modulate; |
241 GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha"); | 237 GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha"); |
242 builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str() ); | 238 builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str() ); |
243 } | 239 } |
244 | 240 |
245 static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrG LCaps&) { | 241 static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrG LCaps&) { |
246 const EllipseEdgeEffect& ellipseEffect = drawEffect.castEffect<Ellip seEdgeEffect>(); | 242 const EllipseEdgeEffect& ellipseEffect = drawEffect.castEffect<Ellip seEdgeEffect>(); |
247 | 243 |
(...skipping 30 matching lines...) Expand all Loading... | |
278 | 274 |
279 GrEffectRef* EllipseEdgeEffect::TestCreate(SkMWCRandom* random, | 275 GrEffectRef* EllipseEdgeEffect::TestCreate(SkMWCRandom* random, |
280 GrContext* context, | 276 GrContext* context, |
281 const GrDrawTargetCaps&, | 277 const GrDrawTargetCaps&, |
282 GrTexture* textures[]) { | 278 GrTexture* textures[]) { |
283 return EllipseEdgeEffect::Create(random->nextBool()); | 279 return EllipseEdgeEffect::Create(random->nextBool()); |
284 } | 280 } |
285 | 281 |
286 /////////////////////////////////////////////////////////////////////////////// | 282 /////////////////////////////////////////////////////////////////////////////// |
287 | 283 |
288 /** | |
289 * The output of this effect is a modulation of the input color and coverage for an axis-aligned | |
290 * ellipse, specified as an offset vector from center and reciprocals of outer a nd inner radii in | |
291 * both x and y directions. | |
292 * | |
293 * This uses a slightly different algorithm than the EllipseEdgeEffect, above. R ather than | |
294 * scaling an ellipse to be a circle, it attempts to find the distance from the offset point to the | |
295 * ellipse by determining where the line through the origin and offset point wou ld cross the | |
296 * ellipse, and computing the distance to that. This is slower but works better for roundrects | |
297 * because the straight edges will be more accurate. | |
298 */ | |
299 | |
300 class AltEllipseEdgeEffect : public GrEffect { | |
301 public: | |
302 static GrEffectRef* Create(bool stroke) { | |
303 // we go through this so we only have one copy of each effect (stroked/f illed) | |
304 GR_CREATE_STATIC_EFFECT(gAltEllipseStrokeEdge, AltEllipseEdgeEffect, (tr ue)); | |
305 GR_CREATE_STATIC_EFFECT(gAltEllipseFillEdge, AltEllipseEdgeEffect, (fals e)); | |
306 | |
307 if (stroke) { | |
308 gAltEllipseStrokeEdge->ref(); | |
309 return gAltEllipseStrokeEdge; | |
310 } else { | |
311 gAltEllipseFillEdge->ref(); | |
312 return gAltEllipseFillEdge; | |
313 } | |
314 } | |
315 | |
316 virtual void getConstantColorComponents(GrColor* color, | |
317 uint32_t* validFlags) const SK_OVERR IDE { | |
318 *validFlags = 0; | |
319 } | |
320 | |
321 virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE { | |
322 return GrTBackendEffectFactory<AltEllipseEdgeEffect>::getInstance(); | |
323 } | |
324 | |
325 virtual ~AltEllipseEdgeEffect() {} | |
326 | |
327 static const char* Name() { return "RRectEdge"; } | |
328 | |
329 inline bool isStroked() const { return fStroke; } | |
330 | |
331 class GLEffect : public GrGLEffect { | |
332 public: | |
333 GLEffect(const GrBackendEffectFactory& factory, const GrDrawEffect&) | |
334 : INHERITED (factory) {} | |
335 | |
336 virtual void emitCode(GrGLShaderBuilder* builder, | |
337 const GrDrawEffect& drawEffect, | |
338 EffectKey key, | |
339 const char* outputColor, | |
340 const char* inputColor, | |
341 const TextureSamplerArray& samplers) SK_OVERRIDE { | |
342 const AltEllipseEdgeEffect& rrectEffect = drawEffect.castEffect<AltE llipseEdgeEffect>(); | |
343 | |
344 const char *vsOffsetName, *fsOffsetName; | |
345 const char *vsRadiiName, *fsRadiiName; | |
346 | |
347 builder->addVarying(kVec2f_GrSLType, "EllipseOffsets", &vsOffsetName , &fsOffsetName); | |
348 const SkString* attr0Name = | |
349 builder->getEffectAttributeName(drawEffect.getVertexAttribIndice s()[0]); | |
350 builder->vsCodeAppendf("\t%s = %s;\n", vsOffsetName, attr0Name->c_st r()); | |
351 | |
352 builder->addVarying(kVec4f_GrSLType, "EllipseRadii", &vsRadiiName, & fsRadiiName); | |
353 const SkString* attr1Name = | |
354 builder->getEffectAttributeName(drawEffect.getVertexAttribIndice s()[1]); | |
355 builder->vsCodeAppendf("\t%s = %s;\n", vsRadiiName, attr1Name->c_str ()); | |
356 | |
357 builder->fsCodeAppend("\tfloat edgeAlpha;\n"); | |
358 // get length of offset | |
359 builder->fsCodeAppendf("\tfloat len = length(%s.xy);\n", fsOffsetNam e); | |
360 | |
361 // for outer curve | |
362 builder->fsCodeAppendf("\tvec2 offset = %s.xy*%s.xy;\n", | |
363 fsOffsetName, fsRadiiName); | |
364 builder->fsCodeAppendf("\tfloat t = inversesqrt(dot(offset.xy, offse t.xy));\n"); | |
365 builder->fsCodeAppend("\tedgeAlpha = clamp(len*t - len, 0.0, 1.0);\n "); | |
366 | |
367 // for inner curve | |
368 if (rrectEffect.isStroked()) { | |
369 builder->fsCodeAppendf("\toffset = %s.xy*%s.zw;\n", | |
370 fsOffsetName, fsRadiiName); | |
371 builder->fsCodeAppendf("\tt = inversesqrt(dot(offset.xy, offset. xy));\n"); | |
372 builder->fsCodeAppend("\tedgeAlpha *= clamp(len - len*t, 0.0, 1. 0);\n"); | |
373 } | |
374 | |
375 SkString modulate; | |
376 GrGLSLModulatef<4>(&modulate, inputColor, "edgeAlpha"); | |
377 builder->fsCodeAppendf("\t%s = %s;\n", outputColor, modulate.c_str() ); | |
378 } | |
379 | |
380 static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrG LCaps&) { | |
381 const AltEllipseEdgeEffect& rrectEffect = drawEffect.castEffect<AltE llipseEdgeEffect>(); | |
382 | |
383 return rrectEffect.isStroked() ? 0x1 : 0x0; | |
384 } | |
385 | |
386 virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_ OVERRIDE { | |
387 } | |
388 | |
389 private: | |
390 typedef GrGLEffect INHERITED; | |
391 }; | |
392 | |
393 private: | |
394 AltEllipseEdgeEffect(bool stroke) : GrEffect() { | |
395 this->addVertexAttrib(kVec2f_GrSLType); | |
396 this->addVertexAttrib(kVec4f_GrSLType); | |
397 fStroke = stroke; | |
398 } | |
399 | |
400 virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE { | |
401 const AltEllipseEdgeEffect& aeee = CastEffect<AltEllipseEdgeEffect>(othe r); | |
402 return aeee.fStroke == fStroke; | |
403 } | |
404 | |
405 bool fStroke; | |
406 | |
407 GR_DECLARE_EFFECT_TEST; | |
408 | |
409 typedef GrEffect INHERITED; | |
410 }; | |
411 | |
412 GR_DEFINE_EFFECT_TEST(AltEllipseEdgeEffect); | |
413 | |
414 GrEffectRef* AltEllipseEdgeEffect::TestCreate(SkMWCRandom* random, | |
415 GrContext* context, | |
416 const GrDrawTargetCaps&, | |
417 GrTexture* textures[]) { | |
418 return AltEllipseEdgeEffect::Create(random->nextBool()); | |
419 } | |
420 | |
421 /////////////////////////////////////////////////////////////////////////////// | |
422 | |
423 bool GrOvalRenderer::drawOval(GrDrawTarget* target, const GrContext* context, bo ol useAA, | 284 bool GrOvalRenderer::drawOval(GrDrawTarget* target, const GrContext* context, bo ol useAA, |
424 const GrRect& oval, const SkStrokeRec& stroke) | 285 const GrRect& oval, const SkStrokeRec& stroke) |
425 { | 286 { |
426 if (!useAA) { | 287 if (!useAA) { |
427 return false; | 288 return false; |
428 } | 289 } |
429 | 290 |
430 const SkMatrix& vm = context->getMatrix(); | 291 const SkMatrix& vm = context->getMatrix(); |
431 | 292 |
432 // we can draw circles | 293 // we can draw circles |
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
582 // do any matrix crunching before we reset the draw state for device coords | 443 // do any matrix crunching before we reset the draw state for device coords |
583 const SkMatrix& vm = drawState->getViewMatrix(); | 444 const SkMatrix& vm = drawState->getViewMatrix(); |
584 GrPoint center = GrPoint::Make(ellipse.centerX(), ellipse.centerY()); | 445 GrPoint center = GrPoint::Make(ellipse.centerX(), ellipse.centerY()); |
585 vm.mapPoints(¢er, 1); | 446 vm.mapPoints(¢er, 1); |
586 SkScalar ellipseXRadius = SkScalarHalf(ellipse.width()); | 447 SkScalar ellipseXRadius = SkScalarHalf(ellipse.width()); |
587 SkScalar ellipseYRadius = SkScalarHalf(ellipse.height()); | 448 SkScalar ellipseYRadius = SkScalarHalf(ellipse.height()); |
588 SkScalar xRadius = SkScalarAbs(vm[SkMatrix::kMScaleX]*ellipseXRadius + | 449 SkScalar xRadius = SkScalarAbs(vm[SkMatrix::kMScaleX]*ellipseXRadius + |
589 vm[SkMatrix::kMSkewY]*ellipseYRadius); | 450 vm[SkMatrix::kMSkewY]*ellipseYRadius); |
590 SkScalar yRadius = SkScalarAbs(vm[SkMatrix::kMSkewX]*ellipseXRadius + | 451 SkScalar yRadius = SkScalarAbs(vm[SkMatrix::kMSkewX]*ellipseXRadius + |
591 vm[SkMatrix::kMScaleY]*ellipseYRadius); | 452 vm[SkMatrix::kMScaleY]*ellipseYRadius); |
592 if (SkScalarDiv(xRadius, yRadius) > 2 || SkScalarDiv(yRadius, xRadius) > 2) { | |
593 return false; | |
594 } | |
595 | 453 |
596 // do (potentially) anisotropic mapping of stroke | 454 // do (potentially) anisotropic mapping of stroke |
597 SkVector scaledStroke; | 455 SkVector scaledStroke; |
598 SkScalar strokeWidth = stroke.getWidth(); | 456 SkScalar strokeWidth = stroke.getWidth(); |
599 scaledStroke.fX = SkScalarAbs(strokeWidth*(vm[SkMatrix::kMScaleX] + vm[SkMat rix::kMSkewY])); | 457 scaledStroke.fX = SkScalarAbs(strokeWidth*(vm[SkMatrix::kMScaleX] + vm[SkMat rix::kMSkewY])); |
600 scaledStroke.fY = SkScalarAbs(strokeWidth*(vm[SkMatrix::kMSkewX] + vm[SkMatr ix::kMScaleY])); | 458 scaledStroke.fY = SkScalarAbs(strokeWidth*(vm[SkMatrix::kMSkewX] + vm[SkMatr ix::kMScaleY])); |
601 | 459 |
460 SkStrokeRec::Style style = stroke.getStyle(); | |
461 bool isStroked = (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairl ine_Style == style); | |
462 | |
463 SkScalar innerXRadius = 0.0f; | |
464 SkScalar innerYRadius = 0.0f; | |
465 if (SkStrokeRec::kFill_Style != style) { | |
466 if (SkScalarNearlyZero(scaledStroke.length())) { | |
467 scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf); | |
468 } else { | |
469 scaledStroke.scale(0.5f); | |
470 } | |
471 | |
472 // we only handle thick strokes for near-circular ellipses | |
473 if (scaledStroke.length() > SK_ScalarHalf && (xRadius/yRadius > 2 || yRa dius/xRadius > 2)) { | |
474 return false; | |
475 } | |
476 | |
477 // we don't handle it if curvature of the stroke is less than curvature of the ellipse | |
robertphillips
2013/05/14 22:44:30
The compiler should do this - but rejigger to remo
jvanverth1
2013/05/15 18:34:12
Good catch, thanks.
| |
478 if (scaledStroke.fX/(scaledStroke.fY*scaledStroke.fY) < xRadius/(yRadius *yRadius) || | |
479 scaledStroke.fY/(scaledStroke.fX*scaledStroke.fX) < yRadius/(xRadius *xRadius)) { | |
480 return false; | |
481 } | |
482 | |
483 // this is legit only if scale & translation (which should be the case a t the moment) | |
robertphillips
2013/05/14 22:44:30
isn't this if test redundant since we're in this b
jvanverth1
2013/05/15 18:34:12
No, because it could also be stroked and filled. H
| |
484 if (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style) { | |
robertphillips
2013/05/14 22:44:30
Do innerXRadius first to match xRadius/yRadius ord
jvanverth1
2013/05/15 18:34:12
Done.
| |
485 innerYRadius = SkMaxScalar(0, yRadius - scaledStroke.fY); | |
486 innerXRadius = SkMaxScalar(0, xRadius - scaledStroke.fX); | |
487 } | |
488 | |
489 xRadius += scaledStroke.fX; | |
490 yRadius += scaledStroke.fY; | |
491 } | |
492 | |
602 GrDrawState::AutoDeviceCoordDraw adcd(drawState); | 493 GrDrawState::AutoDeviceCoordDraw adcd(drawState); |
603 if (!adcd.succeeded()) { | 494 if (!adcd.succeeded()) { |
604 return false; | 495 return false; |
605 } | 496 } |
606 | 497 |
607 drawState->setVertexAttribs<gEllipseVertexAttribs>(SK_ARRAY_COUNT(gEllipseVe rtexAttribs)); | 498 drawState->setVertexAttribs<gEllipseVertexAttribs>(SK_ARRAY_COUNT(gEllipseVe rtexAttribs)); |
608 GrAssert(sizeof(EllipseVertex) == drawState->getVertexSize()); | 499 GrAssert(sizeof(EllipseVertex) == drawState->getVertexSize()); |
609 | 500 |
610 GrDrawTarget::AutoReleaseGeometry geo(target, 4, 0); | 501 GrDrawTarget::AutoReleaseGeometry geo(target, 4, 0); |
611 if (!geo.succeeded()) { | 502 if (!geo.succeeded()) { |
612 GrPrintf("Failed to get space for vertices!\n"); | 503 GrPrintf("Failed to get space for vertices!\n"); |
613 return false; | 504 return false; |
614 } | 505 } |
615 | 506 |
616 EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(geo.vertices()); | 507 EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(geo.vertices()); |
617 | 508 |
618 SkStrokeRec::Style style = stroke.getStyle(); | |
619 bool isStroked = (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairl ine_Style == style); | |
620 enum { | 509 enum { |
621 // the edge effects share this stage with glyph rendering | 510 // the edge effects share this stage with glyph rendering |
622 // (kGlyphMaskStage in GrTextContext) && SW path rendering | 511 // (kGlyphMaskStage in GrTextContext) && SW path rendering |
623 // (kPathMaskStage in GrSWMaskHelper) | 512 // (kPathMaskStage in GrSWMaskHelper) |
624 kEdgeEffectStage = GrPaint::kTotalStages, | 513 kEdgeEffectStage = GrPaint::kTotalStages, |
625 }; | 514 }; |
515 GrEffectRef* effect = EllipseEdgeEffect::Create(isStroked && | |
516 innerXRadius > 0.0 && innerY Radius > 0.0); | |
626 | 517 |
627 GrEffectRef* effect = EllipseEdgeEffect::Create(isStroked); | |
628 static const int kEllipseCenterAttrIndex = 1; | 518 static const int kEllipseCenterAttrIndex = 1; |
629 static const int kEllipseEdgeAttrIndex = 2; | 519 static const int kEllipseEdgeAttrIndex = 2; |
630 drawState->setEffect(kEdgeEffectStage, effect, | 520 drawState->setEffect(kEdgeEffectStage, effect, |
631 kEllipseCenterAttrIndex, kEllipseEdgeAttrIndex)->unref( ); | 521 kEllipseCenterAttrIndex, kEllipseEdgeAttrIndex)->unref( ); |
632 | 522 |
633 SkScalar innerXRadius = 0.0f; | 523 // Compute the reciprocals of the squares of the radii here to save time in the shader |
robertphillips
2013/05/14 22:44:30
I think there is a scalar macro for this
jvanverth1
2013/05/15 18:34:12
Done.
| |
634 SkScalar innerRatio = 1.0f; | 524 SkScalar xRadRecip = SK_Scalar1/xRadius; |
635 | 525 SkScalar yRadRecip = SK_Scalar1/yRadius; |
robertphillips
2013/05/14 22:44:30
What happens when innerXRadius is zero?
jvanverth1
2013/05/15 18:34:12
Added a check above. If it's zero, then we'll use
| |
636 if (SkStrokeRec::kFill_Style != style) { | 526 SkScalar xInnerRadRecip = SK_Scalar1/innerXRadius; |
637 if (SkScalarNearlyZero(scaledStroke.length())) { | 527 SkScalar yInnerRadRecip = SK_Scalar1/innerYRadius; |
638 scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf); | |
639 } else { | |
640 scaledStroke.scale(0.5f); | |
641 } | |
642 | |
643 // this is legit only if scale & translation (which should be the case a t the moment) | |
644 if (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style) { | |
645 SkScalar innerYRadius = SkMaxScalar(0, yRadius - scaledStroke.fY); | |
646 if (innerYRadius > SK_ScalarNearlyZero) { | |
647 innerXRadius = SkMaxScalar(0, xRadius - scaledStroke.fX); | |
648 innerRatio = innerXRadius/innerYRadius; | |
649 } | |
650 } | |
651 xRadius += scaledStroke.fX; | |
652 yRadius += scaledStroke.fY; | |
653 } | |
654 | |
655 SkScalar outerRatio = SkScalarDiv(xRadius, yRadius); | |
656 | 528 |
657 // We've extended the outer x radius out half a pixel to antialias. | 529 // We've extended the outer x radius out half a pixel to antialias. |
658 // This will also expand the rect so all the pixels will be captured. | 530 // This will also expand the rect so all the pixels will be captured. |
531 // TODO: Consider if we should use sqrt(2)/2 instead | |
659 xRadius += SK_ScalarHalf; | 532 xRadius += SK_ScalarHalf; |
660 yRadius += SK_ScalarHalf; | 533 yRadius += SK_ScalarHalf; |
661 innerXRadius -= SK_ScalarHalf; | |
662 | 534 |
663 SkRect bounds = SkRect::MakeLTRB( | 535 SkRect bounds = SkRect::MakeLTRB( |
664 center.fX - xRadius, | 536 center.fX - xRadius, |
665 center.fY - yRadius, | 537 center.fY - yRadius, |
666 center.fX + xRadius, | 538 center.fX + xRadius, |
667 center.fY + yRadius | 539 center.fY + yRadius |
668 ); | 540 ); |
669 | 541 |
670 // The offsets are created by scaling the y radius by the appropriate ratio. This way we end up | |
671 // with a circle equation which can be checked quickly in the shader. We nee d one offset for | |
672 // outer and one for inner because they have different scale factors -- othe rwise we end up with | |
673 // non-uniform strokes. | |
674 verts[0].fPos = SkPoint::Make(bounds.fLeft, bounds.fTop); | 542 verts[0].fPos = SkPoint::Make(bounds.fLeft, bounds.fTop); |
675 verts[0].fOuterXRadius = xRadius; | 543 verts[0].fOffset = SkPoint::Make(-xRadius, -yRadius); |
676 verts[0].fInnerXRadius = innerXRadius; | 544 verts[0].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); |
677 verts[0].fOuterOffset = SkPoint::Make(-xRadius, -outerRatio*yRadius); | 545 verts[0].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); |
678 verts[0].fInnerOffset = SkPoint::Make(-xRadius, -innerRatio*yRadius); | |
679 | 546 |
680 verts[1].fPos = SkPoint::Make(bounds.fRight, bounds.fTop); | 547 verts[1].fPos = SkPoint::Make(bounds.fRight, bounds.fTop); |
681 verts[1].fOuterXRadius = xRadius; | 548 verts[1].fOffset = SkPoint::Make(xRadius, -yRadius); |
682 verts[1].fInnerXRadius = innerXRadius; | 549 verts[1].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); |
683 verts[1].fOuterOffset = SkPoint::Make(xRadius, -outerRatio*yRadius); | 550 verts[1].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); |
684 verts[1].fInnerOffset = SkPoint::Make(xRadius, -innerRatio*yRadius); | |
685 | 551 |
686 verts[2].fPos = SkPoint::Make(bounds.fLeft, bounds.fBottom); | 552 verts[2].fPos = SkPoint::Make(bounds.fLeft, bounds.fBottom); |
687 verts[2].fOuterXRadius = xRadius; | 553 verts[2].fOffset = SkPoint::Make(-xRadius, yRadius); |
688 verts[2].fInnerXRadius = innerXRadius; | 554 verts[2].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); |
689 verts[2].fOuterOffset = SkPoint::Make(-xRadius, outerRatio*yRadius); | 555 verts[2].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); |
690 verts[2].fInnerOffset = SkPoint::Make(-xRadius, innerRatio*yRadius); | |
691 | 556 |
692 verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom); | 557 verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom); |
693 verts[3].fOuterXRadius = xRadius; | 558 verts[3].fOffset = SkPoint::Make(xRadius, yRadius); |
694 verts[3].fInnerXRadius = innerXRadius; | 559 verts[3].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); |
695 verts[3].fOuterOffset = SkPoint::Make(xRadius, outerRatio*yRadius); | 560 verts[3].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); |
696 verts[3].fInnerOffset = SkPoint::Make(xRadius, innerRatio*yRadius); | |
697 | 561 |
698 target->drawNonIndexed(kTriangleStrip_GrPrimitiveType, 0, 4, &bounds); | 562 target->drawNonIndexed(kTriangleStrip_GrPrimitiveType, 0, 4, &bounds); |
699 | 563 |
700 return true; | 564 return true; |
701 } | 565 } |
702 | 566 |
703 /////////////////////////////////////////////////////////////////////////////// | 567 /////////////////////////////////////////////////////////////////////////////// |
704 | 568 |
705 static const uint16_t gRRectIndices[] = { | 569 static const uint16_t gRRectIndices[] = { |
706 // corners | 570 // corners |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
756 // do any matrix crunching before we reset the draw state for device coords | 620 // do any matrix crunching before we reset the draw state for device coords |
757 const SkRect& rrectBounds = rrect.getBounds(); | 621 const SkRect& rrectBounds = rrect.getBounds(); |
758 SkRect bounds; | 622 SkRect bounds; |
759 vm.mapRect(&bounds, rrectBounds); | 623 vm.mapRect(&bounds, rrectBounds); |
760 | 624 |
761 SkVector radii = rrect.getSimpleRadii(); | 625 SkVector radii = rrect.getSimpleRadii(); |
762 SkScalar xRadius = SkScalarAbs(vm[SkMatrix::kMScaleX]*radii.fX + | 626 SkScalar xRadius = SkScalarAbs(vm[SkMatrix::kMScaleX]*radii.fX + |
763 vm[SkMatrix::kMSkewY]*radii.fY); | 627 vm[SkMatrix::kMSkewY]*radii.fY); |
764 SkScalar yRadius = SkScalarAbs(vm[SkMatrix::kMSkewX]*radii.fX + | 628 SkScalar yRadius = SkScalarAbs(vm[SkMatrix::kMSkewX]*radii.fX + |
765 vm[SkMatrix::kMScaleY]*radii.fY); | 629 vm[SkMatrix::kMScaleY]*radii.fY); |
766 // tall or wide quarter-ellipse corners aren't handled | 630 |
767 if (SkScalarDiv(xRadius, yRadius) > 2 || SkScalarDiv(yRadius, xRadius) > 2) { | |
768 return false; | |
769 } | |
770 // if hairline stroke is greater than radius, we don't handle that right now | 631 // if hairline stroke is greater than radius, we don't handle that right now |
771 SkStrokeRec::Style style = stroke.getStyle(); | 632 SkStrokeRec::Style style = stroke.getStyle(); |
772 if (SkStrokeRec::kHairline_Style == style && | 633 if (SkStrokeRec::kHairline_Style == style && |
773 (SK_ScalarHalf >= xRadius || SK_ScalarHalf >= yRadius)) { | 634 (SK_ScalarHalf >= xRadius || SK_ScalarHalf >= yRadius)) { |
774 return false; | 635 return false; |
775 } | 636 } |
776 | 637 |
777 // do (potentially) anisotropic mapping of stroke | 638 // do (potentially) anisotropic mapping of stroke |
778 SkVector scaledStroke; | 639 SkVector scaledStroke; |
779 SkScalar strokeWidth = stroke.getWidth(); | 640 SkScalar strokeWidth = stroke.getWidth(); |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
886 verts->fOuterRadius = outerRadius; | 747 verts->fOuterRadius = outerRadius; |
887 verts->fInnerRadius = innerRadius; | 748 verts->fInnerRadius = innerRadius; |
888 verts++; | 749 verts++; |
889 } | 750 } |
890 | 751 |
891 // drop out the middle quad if we're stroked | 752 // drop out the middle quad if we're stroked |
892 int indexCnt = isStroked ? GR_ARRAY_COUNT(gRRectIndices)-6 : GR_ARRAY_CO UNT(gRRectIndices); | 753 int indexCnt = isStroked ? GR_ARRAY_COUNT(gRRectIndices)-6 : GR_ARRAY_CO UNT(gRRectIndices); |
893 target->setIndexSourceToBuffer(indexBuffer); | 754 target->setIndexSourceToBuffer(indexBuffer); |
894 target->drawIndexed(kTriangles_GrPrimitiveType, 0, 0, 16, indexCnt, &bou nds); | 755 target->drawIndexed(kTriangles_GrPrimitiveType, 0, 0, 16, indexCnt, &bou nds); |
895 | 756 |
896 // otherwise we use the special ellipse renderer | 757 // otherwise we use the ellipse renderer |
897 } else { | 758 } else { |
898 | |
899 drawState->setVertexAttribs<gEllipseVertexAttribs>(SK_ARRAY_COUNT(gEllip seVertexAttribs)); | 759 drawState->setVertexAttribs<gEllipseVertexAttribs>(SK_ARRAY_COUNT(gEllip seVertexAttribs)); |
900 GrAssert(sizeof(RRectVertex) == drawState->getVertexSize()); | 760 GrAssert(sizeof(EllipseVertex) == drawState->getVertexSize()); |
901 | |
902 GrDrawTarget::AutoReleaseGeometry geo(target, 16, 0); | |
903 if (!geo.succeeded()) { | |
904 GrPrintf("Failed to get space for vertices!\n"); | |
905 return false; | |
906 } | |
907 RRectVertex* verts = reinterpret_cast<RRectVertex*>(geo.vertices()); | |
908 | |
909 GrEffectRef* effect = AltEllipseEdgeEffect::Create(isStroked); | |
910 static const int kEllipseOffsetAttrIndex = 1; | |
911 static const int kEllipseRadiiAttrIndex = 2; | |
912 drawState->setEffect(kEdgeEffectStage, effect, | |
913 kEllipseOffsetAttrIndex, kEllipseRadiiAttrIndex)->u nref(); | |
914 | 761 |
915 SkScalar innerXRadius = 0.0f; | 762 SkScalar innerXRadius = 0.0f; |
916 SkScalar innerYRadius = 0.0f; | 763 SkScalar innerYRadius = 0.0f; |
917 | |
918 if (SkStrokeRec::kFill_Style != style) { | 764 if (SkStrokeRec::kFill_Style != style) { |
919 if (SkScalarNearlyZero(scaledStroke.length())) { | 765 if (SkScalarNearlyZero(scaledStroke.length())) { |
920 scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf); | 766 scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf); |
921 } else { | 767 } else { |
922 scaledStroke.scale(0.5f); | 768 scaledStroke.scale(0.5f); |
923 } | 769 } |
924 | 770 |
771 // we only handle thick strokes for near-circular ellipses | |
772 if (scaledStroke.length() > SK_ScalarHalf && | |
773 (xRadius/yRadius > 2 || yRadius/xRadius > 2)) { | |
774 return false; | |
775 } | |
776 | |
777 // we don't handle it if curvature of the stroke is less than curvat ure of the ellipse | |
778 if (scaledStroke.fX/(scaledStroke.fY*scaledStroke.fY) < xRadius/(yRa dius*yRadius) || | |
779 scaledStroke.fY/(scaledStroke.fX*scaledStroke.fX) < yRadius/(xRa dius*xRadius)) { | |
780 return false; | |
781 } | |
782 | |
925 // this is legit only if scale & translation (which should be the ca se at the moment) | 783 // this is legit only if scale & translation (which should be the ca se at the moment) |
926 if (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_St yle == style) { | 784 if (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_St yle == style) { |
927 innerXRadius = SkMaxScalar(0, xRadius - scaledStroke.fX); | 785 innerXRadius = SkMaxScalar(0, xRadius - scaledStroke.fX); |
928 innerYRadius = SkMaxScalar(0, yRadius - scaledStroke.fY); | 786 innerYRadius = SkMaxScalar(0, yRadius - scaledStroke.fY); |
929 } | 787 } |
930 xRadius += scaledStroke.fX; | 788 xRadius += scaledStroke.fX; |
931 yRadius += scaledStroke.fY; | 789 yRadius += scaledStroke.fY; |
932 bounds.outset(scaledStroke.fX, scaledStroke.fY); | 790 bounds.outset(scaledStroke.fX, scaledStroke.fY); |
933 } | 791 } |
934 | 792 |
793 GrDrawTarget::AutoReleaseGeometry geo(target, 16, 0); | |
794 if (!geo.succeeded()) { | |
795 GrPrintf("Failed to get space for vertices!\n"); | |
796 return false; | |
797 } | |
798 EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(geo.vertices()); | |
799 | |
800 GrEffectRef* effect = EllipseEdgeEffect::Create(isStroked); | |
801 static const int kEllipseOffsetAttrIndex = 1; | |
802 static const int kEllipseRadiiAttrIndex = 2; | |
803 drawState->setEffect(kEdgeEffectStage, effect, | |
804 kEllipseOffsetAttrIndex, kEllipseRadiiAttrIndex)->u nref(); | |
805 | |
806 // Compute the reciprocals of the radii here to save time in the shader | |
807 SkScalar xRadRecip = SK_Scalar1/xRadius; | |
808 SkScalar yRadRecip = SK_Scalar1/yRadius; | |
809 SkScalar xInnerRadRecip = SK_Scalar1/innerXRadius; | |
810 SkScalar yInnerRadRecip = SK_Scalar1/innerYRadius; | |
811 | |
935 // Extend the radii out half a pixel to antialias. | 812 // Extend the radii out half a pixel to antialias. |
936 SkScalar xOuterRadius = xRadius + SK_ScalarHalf; | 813 SkScalar xOuterRadius = xRadius + SK_ScalarHalf; |
937 SkScalar yOuterRadius = yRadius + SK_ScalarHalf; | 814 SkScalar yOuterRadius = yRadius + SK_ScalarHalf; |
938 SkScalar xInnerRadius = SkMaxScalar(innerXRadius - SK_ScalarHalf, 0); | |
939 SkScalar yInnerRadius = SkMaxScalar(innerYRadius - SK_ScalarHalf, 0); | |
940 | 815 |
941 // Expand the rect so all the pixels will be captured. | 816 // Expand the rect so all the pixels will be captured. |
942 bounds.outset(SK_ScalarHalf, SK_ScalarHalf); | 817 bounds.outset(SK_ScalarHalf, SK_ScalarHalf); |
943 | 818 |
944 SkScalar yCoords[4] = { | 819 SkScalar yCoords[4] = { |
945 bounds.fTop, | 820 bounds.fTop, |
946 bounds.fTop + yOuterRadius, | 821 bounds.fTop + yOuterRadius, |
947 bounds.fBottom - yOuterRadius, | 822 bounds.fBottom - yOuterRadius, |
948 bounds.fBottom | 823 bounds.fBottom |
949 }; | 824 }; |
950 SkScalar yOuterOffsets[4] = { | 825 SkScalar yOuterOffsets[4] = { |
951 -yOuterRadius, | 826 -yOuterRadius, |
952 SK_ScalarNearlyZero, // we're using inversesqrt() in the shader, so can't be exactly 0 | 827 SK_ScalarNearlyZero, // we're using inversesqrt() in the shader, so can't be exactly 0 |
953 SK_ScalarNearlyZero, | 828 SK_ScalarNearlyZero, |
954 yOuterRadius | 829 yOuterRadius |
955 }; | 830 }; |
956 | 831 |
957 SkScalar recipOuterX = SK_Scalar1/xOuterRadius; | |
958 SkScalar recipOuterY = SK_Scalar1/yOuterRadius; | |
959 SkScalar recipInnerX = SK_Scalar1/xInnerRadius; | |
960 SkScalar recipInnerY = SK_Scalar1/yInnerRadius; | |
961 | |
962 for (int i = 0; i < 4; ++i) { | 832 for (int i = 0; i < 4; ++i) { |
963 verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]); | 833 verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]); |
964 verts->fOffset = SkPoint::Make(-xOuterRadius, yOuterOffsets[i]); | 834 verts->fOffset = SkPoint::Make(-xOuterRadius, yOuterOffsets[i]); |
965 verts->fOuterRadii = SkPoint::Make(recipOuterX, recipOuterY); | 835 verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); |
966 verts->fInnerRadii = SkPoint::Make(recipInnerX, recipInnerY); | 836 verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); |
967 verts++; | 837 verts++; |
968 | 838 |
969 verts->fPos = SkPoint::Make(bounds.fLeft + xOuterRadius, yCoords[i]) ; | 839 verts->fPos = SkPoint::Make(bounds.fLeft + xOuterRadius, yCoords[i]) ; |
970 verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i] ); | 840 verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i] ); |
971 verts->fOuterRadii = SkPoint::Make(recipOuterX, recipOuterY); | 841 verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); |
972 verts->fInnerRadii = SkPoint::Make(recipInnerX, recipInnerY); | 842 verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); |
973 verts++; | 843 verts++; |
974 | 844 |
975 verts->fPos = SkPoint::Make(bounds.fRight - xOuterRadius, yCoords[i] ); | 845 verts->fPos = SkPoint::Make(bounds.fRight - xOuterRadius, yCoords[i] ); |
976 verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i] ); | 846 verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i] ); |
977 verts->fOuterRadii = SkPoint::Make(recipOuterX, recipOuterY); | 847 verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); |
978 verts->fInnerRadii = SkPoint::Make(recipInnerX, recipInnerY); | 848 verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); |
979 verts++; | 849 verts++; |
980 | 850 |
981 verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]); | 851 verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]); |
982 verts->fOffset = SkPoint::Make(xOuterRadius, yOuterOffsets[i]); | 852 verts->fOffset = SkPoint::Make(xOuterRadius, yOuterOffsets[i]); |
983 verts->fOuterRadii = SkPoint::Make(recipOuterX, recipOuterY); | 853 verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); |
984 verts->fInnerRadii = SkPoint::Make(recipInnerX, recipInnerY); | 854 verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); |
985 verts++; | 855 verts++; |
986 } | 856 } |
987 | 857 |
988 // drop out the middle quad if we're stroked | 858 // drop out the middle quad if we're stroked |
989 int indexCnt = isStroked ? GR_ARRAY_COUNT(gRRectIndices)-6 : GR_ARRAY_CO UNT(gRRectIndices); | 859 int indexCnt = isStroked ? GR_ARRAY_COUNT(gRRectIndices)-6 : GR_ARRAY_CO UNT(gRRectIndices); |
990 target->setIndexSourceToBuffer(indexBuffer); | 860 target->setIndexSourceToBuffer(indexBuffer); |
991 target->drawIndexed(kTriangles_GrPrimitiveType, 0, 0, 16, indexCnt, &bou nds); | 861 target->drawIndexed(kTriangles_GrPrimitiveType, 0, 0, 16, indexCnt, &bou nds); |
992 } | 862 } |
993 | 863 |
994 return true; | 864 return true; |
995 } | 865 } |
OLD | NEW |