1 /* 2 * Copyright 2013 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "GrOvalOpFactory.h" 9 #include "GrDrawOpTest.h" 10 #include "GrGeometryProcessor.h" 11 #include "GrOpFlushState.h" 12 #include "GrProcessor.h" 13 #include "GrResourceProvider.h" 14 #include "GrShaderCaps.h" 15 #include "GrStyle.h" 16 #include "SkRRect.h" 17 #include "SkStrokeRec.h" 18 #include "glsl/GrGLSLFragmentShaderBuilder.h" 19 #include "glsl/GrGLSLGeometryProcessor.h" 20 #include "glsl/GrGLSLProgramDataManager.h" 21 #include "glsl/GrGLSLUniformHandler.h" 22 #include "glsl/GrGLSLUtil.h" 23 #include "glsl/GrGLSLVarying.h" 24 #include "glsl/GrGLSLVertexShaderBuilder.h" 25 #include "ops/GrMeshDrawOp.h" 26 #include "ops/GrSimpleMeshDrawOpHelper.h" 27 28 namespace { 29 30 struct EllipseVertex { 31 SkPoint fPos; 32 GrColor fColor; 33 SkPoint fOffset; 34 SkPoint fOuterRadii; 35 SkPoint fInnerRadii; 36 }; 37 38 struct DIEllipseVertex { 39 SkPoint fPos; 40 GrColor fColor; 41 SkPoint fOuterOffset; 42 SkPoint fInnerOffset; 43 }; 44 45 static inline bool circle_stays_circle(const SkMatrix& m) { return m.isSimilarity(); } 46 } 47 48 /////////////////////////////////////////////////////////////////////////////// 49 50 /** 51 * The output of this effect is a modulation of the input color and coverage for a circle. It 52 * operates in a space normalized by the circle radius (outer radius in the case of a stroke) 53 * with origin at the circle center. Three vertex attributes are used: 54 * vec2f : position in device space of the bounding geometry vertices 55 * vec4ub: color 56 * vec4f : (p.xy, outerRad, innerRad) 57 * p is the position in the normalized space. 58 * outerRad is the outerRadius in device space. 59 * innerRad is the innerRadius in normalized space (ignored if not stroking). 60 * Additional clip planes are supported for rendering circular arcs. The additional planes are 61 * either intersected or unioned together. Up to three planes are supported (an initial plane, 62 * a plane intersected with the initial plane, and a plane unioned with the first two). Only two 63 * are useful for any given arc, but having all three in one instance allows combining different 64 * types of arcs. 65 */ 66 67 class CircleGeometryProcessor : public GrGeometryProcessor { 68 public: 69 CircleGeometryProcessor(bool stroke, bool clipPlane, bool isectPlane, bool unionPlane, 70 const SkMatrix& localMatrix) 71 : fLocalMatrix(localMatrix) { 72 this->initClassID<CircleGeometryProcessor>(); 73 fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType, 74 kHigh_GrSLPrecision); 75 fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType); 76 fInCircleEdge = &this->addVertexAttrib("inCircleEdge", kVec4f_GrVertexAttribType, 77 kHigh_GrSLPrecision); 78 if (clipPlane) { 79 fInClipPlane = &this->addVertexAttrib("inClipPlane", kVec3f_GrVertexAttribType); 80 } else { 81 fInClipPlane = nullptr; 82 } 83 if (isectPlane) { 84 fInIsectPlane = &this->addVertexAttrib("inIsectPlane", kVec3f_GrVertexAttribType); 85 } else { 86 fInIsectPlane = nullptr; 87 } 88 if (unionPlane) { 89 fInUnionPlane = &this->addVertexAttrib("inUnionPlane", kVec3f_GrVertexAttribType); 90 } else { 91 fInUnionPlane = nullptr; 92 } 93 fStroke = stroke; 94 } 95 96 ~CircleGeometryProcessor() override {} 97 98 const char* name() const override { return "CircleEdge"; } 99 100 void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { 101 GLSLProcessor::GenKey(*this, caps, b); 102 } 103 104 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override { 105 return new GLSLProcessor(); 106 } 107 108 private: 109 class GLSLProcessor : public GrGLSLGeometryProcessor { 110 public: 111 GLSLProcessor() {} 112 113 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { 114 const CircleGeometryProcessor& cgp = args.fGP.cast<CircleGeometryProcessor>(); 115 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; 116 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; 117 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; 118 GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder; 119 120 // emit attributes 121 varyingHandler->emitAttributes(cgp); 122 fragBuilder->codeAppend("highp vec4 circleEdge;"); 123 varyingHandler->addPassThroughAttribute(cgp.fInCircleEdge, "circleEdge", 124 kHigh_GrSLPrecision); 125 if (cgp.fInClipPlane) { 126 fragBuilder->codeAppend("vec3 clipPlane;"); 127 varyingHandler->addPassThroughAttribute(cgp.fInClipPlane, "clipPlane"); 128 } 129 if (cgp.fInIsectPlane) { 130 SkASSERT(cgp.fInClipPlane); 131 fragBuilder->codeAppend("vec3 isectPlane;"); 132 varyingHandler->addPassThroughAttribute(cgp.fInIsectPlane, "isectPlane"); 133 } 134 if (cgp.fInUnionPlane) { 135 SkASSERT(cgp.fInClipPlane); 136 fragBuilder->codeAppend("vec3 unionPlane;"); 137 varyingHandler->addPassThroughAttribute(cgp.fInUnionPlane, "unionPlane"); 138 } 139 140 // setup pass through color 141 varyingHandler->addPassThroughAttribute(cgp.fInColor, args.fOutputColor); 142 143 // Setup position 144 this->setupPosition(vertBuilder, gpArgs, cgp.fInPosition->fName); 145 146 // emit transforms 147 this->emitTransforms(vertBuilder, 148 varyingHandler, 149 uniformHandler, 150 gpArgs->fPositionVar, 151 cgp.fInPosition->fName, 152 cgp.fLocalMatrix, 153 args.fFPCoordTransformHandler); 154 155 fragBuilder->codeAppend("highp float d = length(circleEdge.xy);"); 156 fragBuilder->codeAppend("float distanceToOuterEdge = circleEdge.z * (1.0 - d);"); 157 fragBuilder->codeAppend("float edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);"); 158 if (cgp.fStroke) { 159 fragBuilder->codeAppend( 160 "float distanceToInnerEdge = circleEdge.z * (d - circleEdge.w);"); 161 fragBuilder->codeAppend("float innerAlpha = clamp(distanceToInnerEdge, 0.0, 1.0);"); 162 fragBuilder->codeAppend("edgeAlpha *= innerAlpha;"); 163 } 164 165 if (cgp.fInClipPlane) { 166 fragBuilder->codeAppend( 167 "float clip = clamp(circleEdge.z * dot(circleEdge.xy, clipPlane.xy) + " 168 "clipPlane.z, 0.0, 1.0);"); 169 if (cgp.fInIsectPlane) { 170 fragBuilder->codeAppend( 171 "clip *= clamp(circleEdge.z * dot(circleEdge.xy, isectPlane.xy) + " 172 "isectPlane.z, 0.0, 1.0);"); 173 } 174 if (cgp.fInUnionPlane) { 175 fragBuilder->codeAppend( 176 "clip += (1.0 - clip)*clamp(circleEdge.z * dot(circleEdge.xy, " 177 "unionPlane.xy) + unionPlane.z, 0.0, 1.0);"); 178 } 179 fragBuilder->codeAppend("edgeAlpha *= clip;"); 180 } 181 fragBuilder->codeAppendf("%s = vec4(edgeAlpha);", args.fOutputCoverage); 182 } 183 184 static void GenKey(const GrGeometryProcessor& gp, 185 const GrShaderCaps&, 186 GrProcessorKeyBuilder* b) { 187 const CircleGeometryProcessor& cgp = gp.cast<CircleGeometryProcessor>(); 188 uint16_t key; 189 key = cgp.fStroke ? 0x01 : 0x0; 190 key |= cgp.fLocalMatrix.hasPerspective() ? 0x02 : 0x0; 191 key |= cgp.fInClipPlane ? 0x04 : 0x0; 192 key |= cgp.fInIsectPlane ? 0x08 : 0x0; 193 key |= cgp.fInUnionPlane ? 0x10 : 0x0; 194 b->add32(key); 195 } 196 197 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc, 198 FPCoordTransformIter&& transformIter) override { 199 this->setTransformDataHelper(primProc.cast<CircleGeometryProcessor>().fLocalMatrix, 200 pdman, &transformIter); 201 } 202 203 private: 204 typedef GrGLSLGeometryProcessor INHERITED; 205 }; 206 207 SkMatrix fLocalMatrix; 208 const Attribute* fInPosition; 209 const Attribute* fInColor; 210 const Attribute* fInCircleEdge; 211 const Attribute* fInClipPlane; 212 const Attribute* fInIsectPlane; 213 const Attribute* fInUnionPlane; 214 bool fStroke; 215 216 GR_DECLARE_GEOMETRY_PROCESSOR_TEST 217 218 typedef GrGeometryProcessor INHERITED; 219 }; 220 221 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(CircleGeometryProcessor); 222 223 #if GR_TEST_UTILS 224 sk_sp<GrGeometryProcessor> CircleGeometryProcessor::TestCreate(GrProcessorTestData* d) { 225 return sk_sp<GrGeometryProcessor>(new CircleGeometryProcessor( 226 d->fRandom->nextBool(), d->fRandom->nextBool(), d->fRandom->nextBool(), 227 d->fRandom->nextBool(), GrTest::TestMatrix(d->fRandom))); 228 } 229 #endif 230 231 /////////////////////////////////////////////////////////////////////////////// 232 233 /** 234 * The output of this effect is a modulation of the input color and coverage for an axis-aligned 235 * ellipse, specified as a 2D offset from center, and the reciprocals of the outer and inner radii, 236 * in both x and y directions. 237 * 238 * We are using an implicit function of x^2/a^2 + y^2/b^2 - 1 = 0. 239 */ 240 241 class EllipseGeometryProcessor : public GrGeometryProcessor { 242 public: 243 EllipseGeometryProcessor(bool stroke, const SkMatrix& localMatrix) : fLocalMatrix(localMatrix) { 244 this->initClassID<EllipseGeometryProcessor>(); 245 fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType); 246 fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType); 247 fInEllipseOffset = &this->addVertexAttrib("inEllipseOffset", kVec2f_GrVertexAttribType); 248 fInEllipseRadii = &this->addVertexAttrib("inEllipseRadii", kVec4f_GrVertexAttribType); 249 fStroke = stroke; 250 } 251 252 ~EllipseGeometryProcessor() override {} 253 254 const char* name() const override { return "EllipseEdge"; } 255 256 void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { 257 GLSLProcessor::GenKey(*this, caps, b); 258 } 259 260 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override { 261 return new GLSLProcessor(); 262 } 263 264 private: 265 class GLSLProcessor : public GrGLSLGeometryProcessor { 266 public: 267 GLSLProcessor() {} 268 269 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { 270 const EllipseGeometryProcessor& egp = args.fGP.cast<EllipseGeometryProcessor>(); 271 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; 272 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; 273 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; 274 275 // emit attributes 276 varyingHandler->emitAttributes(egp); 277 278 GrGLSLVertToFrag ellipseOffsets(kVec2f_GrSLType); 279 varyingHandler->addVarying("EllipseOffsets", &ellipseOffsets); 280 vertBuilder->codeAppendf("%s = %s;", ellipseOffsets.vsOut(), 281 egp.fInEllipseOffset->fName); 282 283 GrGLSLVertToFrag ellipseRadii(kVec4f_GrSLType); 284 varyingHandler->addVarying("EllipseRadii", &ellipseRadii); 285 vertBuilder->codeAppendf("%s = %s;", ellipseRadii.vsOut(), egp.fInEllipseRadii->fName); 286 287 GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder; 288 // setup pass through color 289 varyingHandler->addPassThroughAttribute(egp.fInColor, args.fOutputColor); 290 291 // Setup position 292 this->setupPosition(vertBuilder, gpArgs, egp.fInPosition->fName); 293 294 // emit transforms 295 this->emitTransforms(vertBuilder, 296 varyingHandler, 297 uniformHandler, 298 gpArgs->fPositionVar, 299 egp.fInPosition->fName, 300 egp.fLocalMatrix, 301 args.fFPCoordTransformHandler); 302 303 // for outer curve 304 fragBuilder->codeAppendf("vec2 scaledOffset = %s*%s.xy;", ellipseOffsets.fsIn(), 305 ellipseRadii.fsIn()); 306 fragBuilder->codeAppend("float test = dot(scaledOffset, scaledOffset) - 1.0;"); 307 fragBuilder->codeAppendf("vec2 grad = 2.0*scaledOffset*%s.xy;", ellipseRadii.fsIn()); 308 fragBuilder->codeAppend("float grad_dot = dot(grad, grad);"); 309 310 // avoid calling inversesqrt on zero. 311 fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.0e-4);"); 312 fragBuilder->codeAppend("float invlen = inversesqrt(grad_dot);"); 313 fragBuilder->codeAppend("float edgeAlpha = clamp(0.5-test*invlen, 0.0, 1.0);"); 314 315 // for inner curve 316 if (egp.fStroke) { 317 fragBuilder->codeAppendf("scaledOffset = %s*%s.zw;", ellipseOffsets.fsIn(), 318 ellipseRadii.fsIn()); 319 fragBuilder->codeAppend("test = dot(scaledOffset, scaledOffset) - 1.0;"); 320 fragBuilder->codeAppendf("grad = 2.0*scaledOffset*%s.zw;", ellipseRadii.fsIn()); 321 fragBuilder->codeAppend("invlen = inversesqrt(dot(grad, grad));"); 322 fragBuilder->codeAppend("edgeAlpha *= clamp(0.5+test*invlen, 0.0, 1.0);"); 323 } 324 325 fragBuilder->codeAppendf("%s = vec4(edgeAlpha);", args.fOutputCoverage); 326 } 327 328 static void GenKey(const GrGeometryProcessor& gp, 329 const GrShaderCaps&, 330 GrProcessorKeyBuilder* b) { 331 const EllipseGeometryProcessor& egp = gp.cast<EllipseGeometryProcessor>(); 332 uint16_t key = egp.fStroke ? 0x1 : 0x0; 333 key |= egp.fLocalMatrix.hasPerspective() ? 0x2 : 0x0; 334 b->add32(key); 335 } 336 337 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc, 338 FPCoordTransformIter&& transformIter) override { 339 const EllipseGeometryProcessor& egp = primProc.cast<EllipseGeometryProcessor>(); 340 this->setTransformDataHelper(egp.fLocalMatrix, pdman, &transformIter); 341 } 342 343 private: 344 typedef GrGLSLGeometryProcessor INHERITED; 345 }; 346 347 const Attribute* fInPosition; 348 const Attribute* fInColor; 349 const Attribute* fInEllipseOffset; 350 const Attribute* fInEllipseRadii; 351 SkMatrix fLocalMatrix; 352 bool fStroke; 353 354 GR_DECLARE_GEOMETRY_PROCESSOR_TEST 355 356 typedef GrGeometryProcessor INHERITED; 357 }; 358 359 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(EllipseGeometryProcessor); 360 361 #if GR_TEST_UTILS 362 sk_sp<GrGeometryProcessor> EllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) { 363 return sk_sp<GrGeometryProcessor>( 364 new EllipseGeometryProcessor(d->fRandom->nextBool(), GrTest::TestMatrix(d->fRandom))); 365 } 366 #endif 367 368 /////////////////////////////////////////////////////////////////////////////// 369 370 /** 371 * The output of this effect is a modulation of the input color and coverage for an ellipse, 372 * specified as a 2D offset from center for both the outer and inner paths (if stroked). The 373 * implict equation used is for a unit circle (x^2 + y^2 - 1 = 0) and the edge corrected by 374 * using differentials. 375 * 376 * The result is device-independent and can be used with any affine matrix. 377 */ 378 379 enum class DIEllipseStyle { kStroke = 0, kHairline, kFill }; 380 381 class DIEllipseGeometryProcessor : public GrGeometryProcessor { 382 public: 383 DIEllipseGeometryProcessor(const SkMatrix& viewMatrix, DIEllipseStyle style) 384 : fViewMatrix(viewMatrix) { 385 this->initClassID<DIEllipseGeometryProcessor>(); 386 fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType, 387 kHigh_GrSLPrecision); 388 fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType); 389 fInEllipseOffsets0 = &this->addVertexAttrib("inEllipseOffsets0", kVec2f_GrVertexAttribType); 390 fInEllipseOffsets1 = &this->addVertexAttrib("inEllipseOffsets1", kVec2f_GrVertexAttribType); 391 fStyle = style; 392 } 393 394 ~DIEllipseGeometryProcessor() override {} 395 396 const char* name() const override { return "DIEllipseEdge"; } 397 398 void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { 399 GLSLProcessor::GenKey(*this, caps, b); 400 } 401 402 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override { 403 return new GLSLProcessor(); 404 } 405 406 private: 407 class GLSLProcessor : public GrGLSLGeometryProcessor { 408 public: 409 GLSLProcessor() : fViewMatrix(SkMatrix::InvalidMatrix()) {} 410 411 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { 412 const DIEllipseGeometryProcessor& diegp = args.fGP.cast<DIEllipseGeometryProcessor>(); 413 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; 414 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; 415 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; 416 417 // emit attributes 418 varyingHandler->emitAttributes(diegp); 419 420 GrGLSLVertToFrag offsets0(kVec2f_GrSLType); 421 varyingHandler->addVarying("EllipseOffsets0", &offsets0); 422 vertBuilder->codeAppendf("%s = %s;", offsets0.vsOut(), diegp.fInEllipseOffsets0->fName); 423 424 GrGLSLVertToFrag offsets1(kVec2f_GrSLType); 425 varyingHandler->addVarying("EllipseOffsets1", &offsets1); 426 vertBuilder->codeAppendf("%s = %s;", offsets1.vsOut(), diegp.fInEllipseOffsets1->fName); 427 428 GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder; 429 varyingHandler->addPassThroughAttribute(diegp.fInColor, args.fOutputColor); 430 431 // Setup position 432 this->setupPosition(vertBuilder, 433 uniformHandler, 434 gpArgs, 435 diegp.fInPosition->fName, 436 diegp.fViewMatrix, 437 &fViewMatrixUniform); 438 439 // emit transforms 440 this->emitTransforms(vertBuilder, 441 varyingHandler, 442 uniformHandler, 443 gpArgs->fPositionVar, 444 diegp.fInPosition->fName, 445 args.fFPCoordTransformHandler); 446 447 // for outer curve 448 fragBuilder->codeAppendf("vec2 scaledOffset = %s.xy;", offsets0.fsIn()); 449 fragBuilder->codeAppend("float test = dot(scaledOffset, scaledOffset) - 1.0;"); 450 fragBuilder->codeAppendf("vec2 duvdx = dFdx(%s);", offsets0.fsIn()); 451 fragBuilder->codeAppendf("vec2 duvdy = dFdy(%s);", offsets0.fsIn()); 452 fragBuilder->codeAppendf( 453 "vec2 grad = vec2(2.0*%s.x*duvdx.x + 2.0*%s.y*duvdx.y," 454 " 2.0*%s.x*duvdy.x + 2.0*%s.y*duvdy.y);", 455 offsets0.fsIn(), offsets0.fsIn(), offsets0.fsIn(), offsets0.fsIn()); 456 457 fragBuilder->codeAppend("float grad_dot = dot(grad, grad);"); 458 // avoid calling inversesqrt on zero. 459 fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.0e-4);"); 460 fragBuilder->codeAppend("float invlen = inversesqrt(grad_dot);"); 461 if (DIEllipseStyle::kHairline == diegp.fStyle) { 462 // can probably do this with one step 463 fragBuilder->codeAppend("float edgeAlpha = clamp(1.0-test*invlen, 0.0, 1.0);"); 464 fragBuilder->codeAppend("edgeAlpha *= clamp(1.0+test*invlen, 0.0, 1.0);"); 465 } else { 466 fragBuilder->codeAppend("float edgeAlpha = clamp(0.5-test*invlen, 0.0, 1.0);"); 467 } 468 469 // for inner curve 470 if (DIEllipseStyle::kStroke == diegp.fStyle) { 471 fragBuilder->codeAppendf("scaledOffset = %s.xy;", offsets1.fsIn()); 472 fragBuilder->codeAppend("test = dot(scaledOffset, scaledOffset) - 1.0;"); 473 fragBuilder->codeAppendf("duvdx = dFdx(%s);", offsets1.fsIn()); 474 fragBuilder->codeAppendf("duvdy = dFdy(%s);", offsets1.fsIn()); 475 fragBuilder->codeAppendf( 476 "grad = vec2(2.0*%s.x*duvdx.x + 2.0*%s.y*duvdx.y," 477 " 2.0*%s.x*duvdy.x + 2.0*%s.y*duvdy.y);", 478 offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn()); 479 fragBuilder->codeAppend("invlen = inversesqrt(dot(grad, grad));"); 480 fragBuilder->codeAppend("edgeAlpha *= clamp(0.5+test*invlen, 0.0, 1.0);"); 481 } 482 483 fragBuilder->codeAppendf("%s = vec4(edgeAlpha);", args.fOutputCoverage); 484 } 485 486 static void GenKey(const GrGeometryProcessor& gp, 487 const GrShaderCaps&, 488 GrProcessorKeyBuilder* b) { 489 const DIEllipseGeometryProcessor& diegp = gp.cast<DIEllipseGeometryProcessor>(); 490 uint16_t key = static_cast<uint16_t>(diegp.fStyle); 491 key |= ComputePosKey(diegp.fViewMatrix) << 10; 492 b->add32(key); 493 } 494 495 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& gp, 496 FPCoordTransformIter&& transformIter) override { 497 const DIEllipseGeometryProcessor& diegp = gp.cast<DIEllipseGeometryProcessor>(); 498 499 if (!diegp.fViewMatrix.isIdentity() && !fViewMatrix.cheapEqualTo(diegp.fViewMatrix)) { 500 fViewMatrix = diegp.fViewMatrix; 501 float viewMatrix[3 * 3]; 502 GrGLSLGetMatrix<3>(viewMatrix, fViewMatrix); 503 pdman.setMatrix3f(fViewMatrixUniform, viewMatrix); 504 } 505 this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter); 506 } 507 508 private: 509 SkMatrix fViewMatrix; 510 UniformHandle fViewMatrixUniform; 511 512 typedef GrGLSLGeometryProcessor INHERITED; 513 }; 514 515 const Attribute* fInPosition; 516 const Attribute* fInColor; 517 const Attribute* fInEllipseOffsets0; 518 const Attribute* fInEllipseOffsets1; 519 SkMatrix fViewMatrix; 520 DIEllipseStyle fStyle; 521 522 GR_DECLARE_GEOMETRY_PROCESSOR_TEST 523 524 typedef GrGeometryProcessor INHERITED; 525 }; 526 527 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DIEllipseGeometryProcessor); 528 529 #if GR_TEST_UTILS 530 sk_sp<GrGeometryProcessor> DIEllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) { 531 return sk_sp<GrGeometryProcessor>(new DIEllipseGeometryProcessor( 532 GrTest::TestMatrix(d->fRandom), (DIEllipseStyle)(d->fRandom->nextRangeU(0, 2)))); 533 } 534 #endif 535 536 /////////////////////////////////////////////////////////////////////////////// 537 538 // We have two possible cases for geometry for a circle: 539 540 // In the case of a normal fill, we draw geometry for the circle as an octagon. 541 static const uint16_t gFillCircleIndices[] = { 542 // enter the octagon 543 // clang-format off 544 0, 1, 8, 1, 2, 8, 545 2, 3, 8, 3, 4, 8, 546 4, 5, 8, 5, 6, 8, 547 6, 7, 8, 7, 0, 8 548 // clang-format on 549 }; 550 551 // For stroked circles, we use two nested octagons. 552 static const uint16_t gStrokeCircleIndices[] = { 553 // enter the octagon 554 // clang-format off 555 0, 1, 9, 0, 9, 8, 556 1, 2, 10, 1, 10, 9, 557 2, 3, 11, 2, 11, 10, 558 3, 4, 12, 3, 12, 11, 559 4, 5, 13, 4, 13, 12, 560 5, 6, 14, 5, 14, 13, 561 6, 7, 15, 6, 15, 14, 562 7, 0, 8, 7, 8, 15, 563 // clang-format on 564 }; 565 566 567 static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices); 568 static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices); 569 static const int kVertsPerStrokeCircle = 16; 570 static const int kVertsPerFillCircle = 9; 571 572 static int circle_type_to_vert_count(bool stroked) { 573 return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle; 574 } 575 576 static int circle_type_to_index_count(bool stroked) { 577 return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle; 578 } 579 580 static const uint16_t* circle_type_to_indices(bool stroked) { 581 return stroked ? gStrokeCircleIndices : gFillCircleIndices; 582 } 583 584 /////////////////////////////////////////////////////////////////////////////// 585 586 class CircleOp final : public GrMeshDrawOp { 587 private: 588 using Helper = GrSimpleMeshDrawOpHelper; 589 590 public: 591 DEFINE_OP_CLASS_ID 592 593 /** Optional extra params to render a partial arc rather than a full circle. */ 594 struct ArcParams { 595 SkScalar fStartAngleRadians; 596 SkScalar fSweepAngleRadians; 597 bool fUseCenter; 598 }; 599 600 static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, const SkMatrix& viewMatrix, 601 SkPoint center, SkScalar radius, const GrStyle& style, 602 const ArcParams* arcParams = nullptr) { 603 SkASSERT(circle_stays_circle(viewMatrix)); 604 if (style.hasPathEffect()) { 605 return nullptr; 606 } 607 const SkStrokeRec& stroke = style.strokeRec(); 608 SkStrokeRec::Style recStyle = stroke.getStyle(); 609 if (arcParams) { 610 // Arc support depends on the style. 611 switch (recStyle) { 612 case SkStrokeRec::kStrokeAndFill_Style: 613 // This produces a strange result that this op doesn't implement. 614 return nullptr; 615 case SkStrokeRec::kFill_Style: 616 // This supports all fills. 617 break; 618 case SkStrokeRec::kStroke_Style: // fall through 619 case SkStrokeRec::kHairline_Style: 620 // Strokes that don't use the center point are supported with butt cap. 621 if (arcParams->fUseCenter || stroke.getCap() != SkPaint::kButt_Cap) { 622 return nullptr; 623 } 624 break; 625 } 626 } 627 return Helper::FactoryHelper<CircleOp>(std::move(paint), viewMatrix, center, radius, style, 628 arcParams); 629 } 630 631 CircleOp(const Helper::MakeArgs& helperArgs, GrColor color, const SkMatrix& viewMatrix, 632 SkPoint center, SkScalar radius, const GrStyle& style, const ArcParams* arcParams) 633 : GrMeshDrawOp(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) { 634 const SkStrokeRec& stroke = style.strokeRec(); 635 SkStrokeRec::Style recStyle = stroke.getStyle(); 636 637 viewMatrix.mapPoints(¢er, 1); 638 radius = viewMatrix.mapRadius(radius); 639 SkScalar strokeWidth = viewMatrix.mapRadius(stroke.getWidth()); 640 641 bool isStrokeOnly = 642 SkStrokeRec::kStroke_Style == recStyle || SkStrokeRec::kHairline_Style == recStyle; 643 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == recStyle; 644 645 SkScalar innerRadius = -SK_ScalarHalf; 646 SkScalar outerRadius = radius; 647 SkScalar halfWidth = 0; 648 if (hasStroke) { 649 if (SkScalarNearlyZero(strokeWidth)) { 650 halfWidth = SK_ScalarHalf; 651 } else { 652 halfWidth = SkScalarHalf(strokeWidth); 653 } 654 655 outerRadius += halfWidth; 656 if (isStrokeOnly) { 657 innerRadius = radius - halfWidth; 658 } 659 } 660 661 // The radii are outset for two reasons. First, it allows the shader to simply perform 662 // simpler computation because the computed alpha is zero, rather than 50%, at the radius. 663 // Second, the outer radius is used to compute the verts of the bounding box that is 664 // rendered and the outset ensures the box will cover all partially covered by the circle. 665 outerRadius += SK_ScalarHalf; 666 innerRadius -= SK_ScalarHalf; 667 bool stroked = isStrokeOnly && innerRadius > 0.0f; 668 fViewMatrixIfUsingLocalCoords = viewMatrix; 669 670 // This makes every point fully inside the intersection plane. 671 static constexpr SkScalar kUnusedIsectPlane[] = {0.f, 0.f, 1.f}; 672 // This makes every point fully outside the union plane. 673 static constexpr SkScalar kUnusedUnionPlane[] = {0.f, 0.f, 0.f}; 674 SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius, 675 center.fX + outerRadius, center.fY + outerRadius); 676 if (arcParams) { 677 // The shader operates in a space where the circle is translated to be centered at the 678 // origin. Here we compute points on the unit circle at the starting and ending angles. 679 SkPoint startPoint, stopPoint; 680 startPoint.fY = SkScalarSinCos(arcParams->fStartAngleRadians, &startPoint.fX); 681 SkScalar endAngle = arcParams->fStartAngleRadians + arcParams->fSweepAngleRadians; 682 stopPoint.fY = SkScalarSinCos(endAngle, &stopPoint.fX); 683 684 // Adjust the start and end points based on the view matrix (to handle rotated arcs) 685 startPoint = viewMatrix.mapVector(startPoint.fX, startPoint.fY); 686 stopPoint = viewMatrix.mapVector(stopPoint.fX, stopPoint.fY); 687 startPoint.normalize(); 688 stopPoint.normalize(); 689 690 // If the matrix included scale (on one axis) we need to swap our start and end points 691 if ((viewMatrix.getScaleX() < 0) != (viewMatrix.getScaleY() < 0)) { 692 SkTSwap(startPoint, stopPoint); 693 } 694 695 // Like a fill without useCenter, butt-cap stroke can be implemented by clipping against 696 // radial lines. However, in both cases we have to be careful about the half-circle. 697 // case. In that case the two radial lines are equal and so that edge gets clipped 698 // twice. Since the shared edge goes through the center we fall back on the useCenter 699 // case. 700 bool useCenter = 701 (arcParams->fUseCenter || isStrokeOnly) && 702 !SkScalarNearlyEqual(SkScalarAbs(arcParams->fSweepAngleRadians), SK_ScalarPI); 703 if (useCenter) { 704 SkVector norm0 = {startPoint.fY, -startPoint.fX}; 705 SkVector norm1 = {stopPoint.fY, -stopPoint.fX}; 706 if (arcParams->fSweepAngleRadians > 0) { 707 norm0.negate(); 708 } else { 709 norm1.negate(); 710 } 711 fClipPlane = true; 712 if (SkScalarAbs(arcParams->fSweepAngleRadians) > SK_ScalarPI) { 713 fCircles.emplace_back(Circle{ 714 color, 715 innerRadius, 716 outerRadius, 717 {norm0.fX, norm0.fY, 0.5f}, 718 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]}, 719 {norm1.fX, norm1.fY, 0.5f}, 720 devBounds, 721 stroked}); 722 fClipPlaneIsect = false; 723 fClipPlaneUnion = true; 724 } else { 725 fCircles.emplace_back(Circle{ 726 color, 727 innerRadius, 728 outerRadius, 729 {norm0.fX, norm0.fY, 0.5f}, 730 {norm1.fX, norm1.fY, 0.5f}, 731 {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]}, 732 devBounds, 733 stroked}); 734 fClipPlaneIsect = true; 735 fClipPlaneUnion = false; 736 } 737 } else { 738 // We clip to a secant of the original circle. 739 startPoint.scale(radius); 740 stopPoint.scale(radius); 741 SkVector norm = {startPoint.fY - stopPoint.fY, stopPoint.fX - startPoint.fX}; 742 norm.normalize(); 743 if (arcParams->fSweepAngleRadians > 0) { 744 norm.negate(); 745 } 746 SkScalar d = -norm.dot(startPoint) + 0.5f; 747 748 fCircles.emplace_back( 749 Circle{color, 750 innerRadius, 751 outerRadius, 752 {norm.fX, norm.fY, d}, 753 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]}, 754 {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]}, 755 devBounds, 756 stroked}); 757 fClipPlane = true; 758 fClipPlaneIsect = false; 759 fClipPlaneUnion = false; 760 } 761 } else { 762 fCircles.emplace_back( 763 Circle{color, 764 innerRadius, 765 outerRadius, 766 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]}, 767 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]}, 768 {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]}, 769 devBounds, 770 stroked}); 771 fClipPlane = false; 772 fClipPlaneIsect = false; 773 fClipPlaneUnion = false; 774 } 775 // Use the original radius and stroke radius for the bounds so that it does not include the 776 // AA bloat. 777 radius += halfWidth; 778 this->setBounds( 779 {center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius}, 780 HasAABloat::kYes, IsZeroArea::kNo); 781 fVertCount = circle_type_to_vert_count(stroked); 782 fIndexCount = circle_type_to_index_count(stroked); 783 fAllFill = !stroked; 784 } 785 786 const char* name() const override { return "CircleOp"; } 787 788 SkString dumpInfo() const override { 789 SkString string; 790 for (int i = 0; i < fCircles.count(); ++i) { 791 string.appendf( 792 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]," 793 "InnerRad: %.2f, OuterRad: %.2f\n", 794 fCircles[i].fColor, fCircles[i].fDevBounds.fLeft, fCircles[i].fDevBounds.fTop, 795 fCircles[i].fDevBounds.fRight, fCircles[i].fDevBounds.fBottom, 796 fCircles[i].fInnerRadius, fCircles[i].fOuterRadius); 797 } 798 string += fHelper.dumpInfo(); 799 string += INHERITED::dumpInfo(); 800 return string; 801 } 802 803 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override { 804 GrColor* color = &fCircles.front().fColor; 805 return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel, 806 color); 807 } 808 809 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 810 811 private: 812 void onPrepareDraws(Target* target) const override { 813 SkMatrix localMatrix; 814 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { 815 return; 816 } 817 818 // Setup geometry processor 819 sk_sp<GrGeometryProcessor> gp(new CircleGeometryProcessor( 820 !fAllFill, fClipPlane, fClipPlaneIsect, fClipPlaneUnion, localMatrix)); 821 822 struct CircleVertex { 823 SkPoint fPos; 824 GrColor fColor; 825 SkPoint fOffset; 826 SkScalar fOuterRadius; 827 SkScalar fInnerRadius; 828 // These planes may or may not be present in the vertex buffer. 829 SkScalar fHalfPlanes[3][3]; 830 }; 831 832 size_t vertexStride = gp->getVertexStride(); 833 SkASSERT(vertexStride == 834 sizeof(CircleVertex) - (fClipPlane ? 0 : 3 * sizeof(SkScalar)) - 835 (fClipPlaneIsect ? 0 : 3 * sizeof(SkScalar)) - 836 (fClipPlaneUnion ? 0 : 3 * sizeof(SkScalar))); 837 838 const GrBuffer* vertexBuffer; 839 int firstVertex; 840 char* vertices = (char*)target->makeVertexSpace(vertexStride, fVertCount, &vertexBuffer, 841 &firstVertex); 842 if (!vertices) { 843 SkDebugf("Could not allocate vertices\n"); 844 return; 845 } 846 847 const GrBuffer* indexBuffer = nullptr; 848 int firstIndex = 0; 849 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex); 850 if (!indices) { 851 SkDebugf("Could not allocate indices\n"); 852 return; 853 } 854 855 int currStartVertex = 0; 856 for (const auto& circle : fCircles) { 857 SkScalar innerRadius = circle.fInnerRadius; 858 SkScalar outerRadius = circle.fOuterRadius; 859 GrColor color = circle.fColor; 860 const SkRect& bounds = circle.fDevBounds; 861 862 CircleVertex* v0 = reinterpret_cast<CircleVertex*>(vertices + 0 * vertexStride); 863 CircleVertex* v1 = reinterpret_cast<CircleVertex*>(vertices + 1 * vertexStride); 864 CircleVertex* v2 = reinterpret_cast<CircleVertex*>(vertices + 2 * vertexStride); 865 CircleVertex* v3 = reinterpret_cast<CircleVertex*>(vertices + 3 * vertexStride); 866 CircleVertex* v4 = reinterpret_cast<CircleVertex*>(vertices + 4 * vertexStride); 867 CircleVertex* v5 = reinterpret_cast<CircleVertex*>(vertices + 5 * vertexStride); 868 CircleVertex* v6 = reinterpret_cast<CircleVertex*>(vertices + 6 * vertexStride); 869 CircleVertex* v7 = reinterpret_cast<CircleVertex*>(vertices + 7 * vertexStride); 870 871 // The inner radius in the vertex data must be specified in normalized space. 872 innerRadius = innerRadius / outerRadius; 873 874 SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY()); 875 SkScalar halfWidth = 0.5f * bounds.width(); 876 SkScalar octOffset = 0.41421356237f; // sqrt(2) - 1 877 878 v0->fPos = center + SkPoint::Make(-octOffset * halfWidth, -halfWidth); 879 v0->fColor = color; 880 v0->fOffset = SkPoint::Make(-octOffset, -1); 881 v0->fOuterRadius = outerRadius; 882 v0->fInnerRadius = innerRadius; 883 884 v1->fPos = center + SkPoint::Make(octOffset * halfWidth, -halfWidth); 885 v1->fColor = color; 886 v1->fOffset = SkPoint::Make(octOffset, -1); 887 v1->fOuterRadius = outerRadius; 888 v1->fInnerRadius = innerRadius; 889 890 v2->fPos = center + SkPoint::Make(halfWidth, -octOffset * halfWidth); 891 v2->fColor = color; 892 v2->fOffset = SkPoint::Make(1, -octOffset); 893 v2->fOuterRadius = outerRadius; 894 v2->fInnerRadius = innerRadius; 895 896 v3->fPos = center + SkPoint::Make(halfWidth, octOffset * halfWidth); 897 v3->fColor = color; 898 v3->fOffset = SkPoint::Make(1, octOffset); 899 v3->fOuterRadius = outerRadius; 900 v3->fInnerRadius = innerRadius; 901 902 v4->fPos = center + SkPoint::Make(octOffset * halfWidth, halfWidth); 903 v4->fColor = color; 904 v4->fOffset = SkPoint::Make(octOffset, 1); 905 v4->fOuterRadius = outerRadius; 906 v4->fInnerRadius = innerRadius; 907 908 v5->fPos = center + SkPoint::Make(-octOffset * halfWidth, halfWidth); 909 v5->fColor = color; 910 v5->fOffset = SkPoint::Make(-octOffset, 1); 911 v5->fOuterRadius = outerRadius; 912 v5->fInnerRadius = innerRadius; 913 914 v6->fPos = center + SkPoint::Make(-halfWidth, octOffset * halfWidth); 915 v6->fColor = color; 916 v6->fOffset = SkPoint::Make(-1, octOffset); 917 v6->fOuterRadius = outerRadius; 918 v6->fInnerRadius = innerRadius; 919 920 v7->fPos = center + SkPoint::Make(-halfWidth, -octOffset * halfWidth); 921 v7->fColor = color; 922 v7->fOffset = SkPoint::Make(-1, -octOffset); 923 v7->fOuterRadius = outerRadius; 924 v7->fInnerRadius = innerRadius; 925 926 if (fClipPlane) { 927 memcpy(v0->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 928 memcpy(v1->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 929 memcpy(v2->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 930 memcpy(v3->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 931 memcpy(v4->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 932 memcpy(v5->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 933 memcpy(v6->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 934 memcpy(v7->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 935 } 936 int unionIdx = 1; 937 if (fClipPlaneIsect) { 938 memcpy(v0->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 939 memcpy(v1->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 940 memcpy(v2->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 941 memcpy(v3->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 942 memcpy(v4->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 943 memcpy(v5->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 944 memcpy(v6->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 945 memcpy(v7->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 946 unionIdx = 2; 947 } 948 if (fClipPlaneUnion) { 949 memcpy(v0->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 950 memcpy(v1->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 951 memcpy(v2->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 952 memcpy(v3->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 953 memcpy(v4->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 954 memcpy(v5->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 955 memcpy(v6->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 956 memcpy(v7->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 957 } 958 959 if (circle.fStroked) { 960 // compute the inner ring 961 CircleVertex* v0 = reinterpret_cast<CircleVertex*>(vertices + 8 * vertexStride); 962 CircleVertex* v1 = reinterpret_cast<CircleVertex*>(vertices + 9 * vertexStride); 963 CircleVertex* v2 = reinterpret_cast<CircleVertex*>(vertices + 10 * vertexStride); 964 CircleVertex* v3 = reinterpret_cast<CircleVertex*>(vertices + 11 * vertexStride); 965 CircleVertex* v4 = reinterpret_cast<CircleVertex*>(vertices + 12 * vertexStride); 966 CircleVertex* v5 = reinterpret_cast<CircleVertex*>(vertices + 13 * vertexStride); 967 CircleVertex* v6 = reinterpret_cast<CircleVertex*>(vertices + 14 * vertexStride); 968 CircleVertex* v7 = reinterpret_cast<CircleVertex*>(vertices + 15 * vertexStride); 969 970 // cosine and sine of pi/8 971 SkScalar c = 0.923579533f; 972 SkScalar s = 0.382683432f; 973 SkScalar r = circle.fInnerRadius; 974 975 v0->fPos = center + SkPoint::Make(-s * r, -c * r); 976 v0->fColor = color; 977 v0->fOffset = SkPoint::Make(-s * innerRadius, -c * innerRadius); 978 v0->fOuterRadius = outerRadius; 979 v0->fInnerRadius = innerRadius; 980 981 v1->fPos = center + SkPoint::Make(s * r, -c * r); 982 v1->fColor = color; 983 v1->fOffset = SkPoint::Make(s * innerRadius, -c * innerRadius); 984 v1->fOuterRadius = outerRadius; 985 v1->fInnerRadius = innerRadius; 986 987 v2->fPos = center + SkPoint::Make(c * r, -s * r); 988 v2->fColor = color; 989 v2->fOffset = SkPoint::Make(c * innerRadius, -s * innerRadius); 990 v2->fOuterRadius = outerRadius; 991 v2->fInnerRadius = innerRadius; 992 993 v3->fPos = center + SkPoint::Make(c * r, s * r); 994 v3->fColor = color; 995 v3->fOffset = SkPoint::Make(c * innerRadius, s * innerRadius); 996 v3->fOuterRadius = outerRadius; 997 v3->fInnerRadius = innerRadius; 998 999 v4->fPos = center + SkPoint::Make(s * r, c * r); 1000 v4->fColor = color; 1001 v4->fOffset = SkPoint::Make(s * innerRadius, c * innerRadius); 1002 v4->fOuterRadius = outerRadius; 1003 v4->fInnerRadius = innerRadius; 1004 1005 v5->fPos = center + SkPoint::Make(-s * r, c * r); 1006 v5->fColor = color; 1007 v5->fOffset = SkPoint::Make(-s * innerRadius, c * innerRadius); 1008 v5->fOuterRadius = outerRadius; 1009 v5->fInnerRadius = innerRadius; 1010 1011 v6->fPos = center + SkPoint::Make(-c * r, s * r); 1012 v6->fColor = color; 1013 v6->fOffset = SkPoint::Make(-c * innerRadius, s * innerRadius); 1014 v6->fOuterRadius = outerRadius; 1015 v6->fInnerRadius = innerRadius; 1016 1017 v7->fPos = center + SkPoint::Make(-c * r, -s * r); 1018 v7->fColor = color; 1019 v7->fOffset = SkPoint::Make(-c * innerRadius, -s * innerRadius); 1020 v7->fOuterRadius = outerRadius; 1021 v7->fInnerRadius = innerRadius; 1022 1023 if (fClipPlane) { 1024 memcpy(v0->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 1025 memcpy(v1->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 1026 memcpy(v2->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 1027 memcpy(v3->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 1028 memcpy(v4->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 1029 memcpy(v5->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 1030 memcpy(v6->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 1031 memcpy(v7->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 1032 } 1033 int unionIdx = 1; 1034 if (fClipPlaneIsect) { 1035 memcpy(v0->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 1036 memcpy(v1->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 1037 memcpy(v2->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 1038 memcpy(v3->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 1039 memcpy(v4->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 1040 memcpy(v5->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 1041 memcpy(v6->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 1042 memcpy(v7->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 1043 unionIdx = 2; 1044 } 1045 if (fClipPlaneUnion) { 1046 memcpy(v0->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 1047 memcpy(v1->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 1048 memcpy(v2->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 1049 memcpy(v3->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 1050 memcpy(v4->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 1051 memcpy(v5->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 1052 memcpy(v6->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 1053 memcpy(v7->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 1054 } 1055 } else { 1056 // filled 1057 CircleVertex* v8 = reinterpret_cast<CircleVertex*>(vertices + 8 * vertexStride); 1058 v8->fPos = center; 1059 v8->fColor = color; 1060 v8->fOffset = SkPoint::Make(0, 0); 1061 v8->fOuterRadius = outerRadius; 1062 v8->fInnerRadius = innerRadius; 1063 if (fClipPlane) { 1064 memcpy(v8->fHalfPlanes[0], circle.fClipPlane, 3 * sizeof(SkScalar)); 1065 } 1066 int unionIdx = 1; 1067 if (fClipPlaneIsect) { 1068 memcpy(v8->fHalfPlanes[1], circle.fIsectPlane, 3 * sizeof(SkScalar)); 1069 unionIdx = 2; 1070 } 1071 if (fClipPlaneUnion) { 1072 memcpy(v8->fHalfPlanes[unionIdx], circle.fUnionPlane, 3 * sizeof(SkScalar)); 1073 } 1074 } 1075 1076 const uint16_t* primIndices = circle_type_to_indices(circle.fStroked); 1077 const int primIndexCount = circle_type_to_index_count(circle.fStroked); 1078 for (int i = 0; i < primIndexCount; ++i) { 1079 *indices++ = primIndices[i] + currStartVertex; 1080 } 1081 1082 currStartVertex += circle_type_to_vert_count(circle.fStroked); 1083 vertices += circle_type_to_vert_count(circle.fStroked) * vertexStride; 1084 } 1085 1086 GrMesh mesh(GrPrimitiveType::kTriangles); 1087 mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertCount - 1); 1088 mesh.setVertexData(vertexBuffer, firstVertex); 1089 target->draw(gp.get(), fHelper.makePipeline(target), mesh); 1090 } 1091 1092 bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override { 1093 CircleOp* that = t->cast<CircleOp>(); 1094 1095 // can only represent 65535 unique vertices with 16-bit indices 1096 if (fVertCount + that->fVertCount > 65536) { 1097 return false; 1098 } 1099 1100 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 1101 return false; 1102 } 1103 1104 if (fHelper.usesLocalCoords() && 1105 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) { 1106 return false; 1107 } 1108 1109 // Because we've set up the ops that don't use the planes with noop values 1110 // we can just accumulate used planes by later ops. 1111 fClipPlane |= that->fClipPlane; 1112 fClipPlaneIsect |= that->fClipPlaneIsect; 1113 fClipPlaneUnion |= that->fClipPlaneUnion; 1114 1115 fCircles.push_back_n(that->fCircles.count(), that->fCircles.begin()); 1116 this->joinBounds(*that); 1117 fVertCount += that->fVertCount; 1118 fIndexCount += that->fIndexCount; 1119 fAllFill = fAllFill && that->fAllFill; 1120 return true; 1121 } 1122 1123 struct Circle { 1124 GrColor fColor; 1125 SkScalar fInnerRadius; 1126 SkScalar fOuterRadius; 1127 SkScalar fClipPlane[3]; 1128 SkScalar fIsectPlane[3]; 1129 SkScalar fUnionPlane[3]; 1130 SkRect fDevBounds; 1131 bool fStroked; 1132 }; 1133 1134 SkMatrix fViewMatrixIfUsingLocalCoords; 1135 Helper fHelper; 1136 SkSTArray<1, Circle, true> fCircles; 1137 int fVertCount; 1138 int fIndexCount; 1139 bool fAllFill; 1140 bool fClipPlane; 1141 bool fClipPlaneIsect; 1142 bool fClipPlaneUnion; 1143 1144 typedef GrMeshDrawOp INHERITED; 1145 }; 1146 1147 /////////////////////////////////////////////////////////////////////////////// 1148 1149 class EllipseOp : public GrMeshDrawOp { 1150 private: 1151 using Helper = GrSimpleMeshDrawOpHelper; 1152 1153 struct DeviceSpaceParams { 1154 SkPoint fCenter; 1155 SkScalar fXRadius; 1156 SkScalar fYRadius; 1157 SkScalar fInnerXRadius; 1158 SkScalar fInnerYRadius; 1159 }; 1160 1161 public: 1162 DEFINE_OP_CLASS_ID 1163 static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, const SkMatrix& viewMatrix, 1164 const SkRect& ellipse, const SkStrokeRec& stroke) { 1165 DeviceSpaceParams params; 1166 // do any matrix crunching before we reset the draw state for device coords 1167 params.fCenter = SkPoint::Make(ellipse.centerX(), ellipse.centerY()); 1168 viewMatrix.mapPoints(¶ms.fCenter, 1); 1169 SkScalar ellipseXRadius = SkScalarHalf(ellipse.width()); 1170 SkScalar ellipseYRadius = SkScalarHalf(ellipse.height()); 1171 params.fXRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * ellipseXRadius + 1172 viewMatrix[SkMatrix::kMSkewX] * ellipseYRadius); 1173 params.fYRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewY] * ellipseXRadius + 1174 viewMatrix[SkMatrix::kMScaleY] * ellipseYRadius); 1175 1176 // do (potentially) anisotropic mapping of stroke 1177 SkVector scaledStroke; 1178 SkScalar strokeWidth = stroke.getWidth(); 1179 scaledStroke.fX = SkScalarAbs( 1180 strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY])); 1181 scaledStroke.fY = SkScalarAbs( 1182 strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY])); 1183 1184 SkStrokeRec::Style style = stroke.getStyle(); 1185 bool isStrokeOnly = 1186 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style; 1187 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style; 1188 1189 params.fInnerXRadius = 0; 1190 params.fInnerYRadius = 0; 1191 if (hasStroke) { 1192 if (SkScalarNearlyZero(scaledStroke.length())) { 1193 scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf); 1194 } else { 1195 scaledStroke.scale(SK_ScalarHalf); 1196 } 1197 1198 // we only handle thick strokes for near-circular ellipses 1199 if (scaledStroke.length() > SK_ScalarHalf && 1200 (0.5f * params.fXRadius > params.fYRadius || 1201 0.5f * params.fYRadius > params.fXRadius)) { 1202 return nullptr; 1203 } 1204 1205 // we don't handle it if curvature of the stroke is less than curvature of the ellipse 1206 if (scaledStroke.fX * (params.fXRadius * params.fYRadius) < 1207 (scaledStroke.fY * scaledStroke.fY) * params.fXRadius || 1208 scaledStroke.fY * (params.fXRadius * params.fXRadius) < 1209 (scaledStroke.fX * scaledStroke.fX) * params.fYRadius) { 1210 return nullptr; 1211 } 1212 1213 // this is legit only if scale & translation (which should be the case at the moment) 1214 if (isStrokeOnly) { 1215 params.fInnerXRadius = params.fXRadius - scaledStroke.fX; 1216 params.fInnerYRadius = params.fYRadius - scaledStroke.fY; 1217 } 1218 1219 params.fXRadius += scaledStroke.fX; 1220 params.fYRadius += scaledStroke.fY; 1221 } 1222 return Helper::FactoryHelper<EllipseOp>(std::move(paint), viewMatrix, params, stroke); 1223 } 1224 1225 EllipseOp(const Helper::MakeArgs& helperArgs, GrColor color, const SkMatrix& viewMatrix, 1226 const DeviceSpaceParams& params, const SkStrokeRec& stroke) 1227 : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) { 1228 SkStrokeRec::Style style = stroke.getStyle(); 1229 bool isStrokeOnly = 1230 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style; 1231 1232 fEllipses.emplace_back(Ellipse{color, params.fXRadius, params.fYRadius, 1233 params.fInnerXRadius, params.fInnerYRadius, 1234 SkRect::MakeLTRB(params.fCenter.fX - params.fXRadius, 1235 params.fCenter.fY - params.fYRadius, 1236 params.fCenter.fX + params.fXRadius, 1237 params.fCenter.fY + params.fYRadius)}); 1238 1239 this->setBounds(fEllipses.back().fDevBounds, HasAABloat::kYes, IsZeroArea::kNo); 1240 1241 // Outset bounds to include half-pixel width antialiasing. 1242 fEllipses[0].fDevBounds.outset(SK_ScalarHalf, SK_ScalarHalf); 1243 1244 fStroked = isStrokeOnly && params.fInnerXRadius > 0 && params.fInnerYRadius > 0; 1245 fViewMatrixIfUsingLocalCoords = viewMatrix; 1246 } 1247 1248 const char* name() const override { return "EllipseOp"; } 1249 1250 SkString dumpInfo() const override { 1251 SkString string; 1252 string.appendf("Stroked: %d\n", fStroked); 1253 for (const auto& geo : fEllipses) { 1254 string.appendf( 1255 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], " 1256 "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n", 1257 geo.fColor, geo.fDevBounds.fLeft, geo.fDevBounds.fTop, geo.fDevBounds.fRight, 1258 geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius, geo.fInnerXRadius, 1259 geo.fInnerYRadius); 1260 } 1261 string += fHelper.dumpInfo(); 1262 string += INHERITED::dumpInfo(); 1263 return string; 1264 } 1265 1266 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override { 1267 GrColor* color = &fEllipses.front().fColor; 1268 return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel, 1269 color); 1270 } 1271 1272 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 1273 1274 private: 1275 void onPrepareDraws(Target* target) const override { 1276 SkMatrix localMatrix; 1277 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { 1278 return; 1279 } 1280 1281 // Setup geometry processor 1282 sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, localMatrix)); 1283 1284 QuadHelper helper; 1285 size_t vertexStride = gp->getVertexStride(); 1286 SkASSERT(vertexStride == sizeof(EllipseVertex)); 1287 EllipseVertex* verts = reinterpret_cast<EllipseVertex*>( 1288 helper.init(target, vertexStride, fEllipses.count())); 1289 if (!verts) { 1290 return; 1291 } 1292 1293 for (const auto& ellipse : fEllipses) { 1294 GrColor color = ellipse.fColor; 1295 SkScalar xRadius = ellipse.fXRadius; 1296 SkScalar yRadius = ellipse.fYRadius; 1297 1298 // Compute the reciprocals of the radii here to save time in the shader 1299 SkScalar xRadRecip = SkScalarInvert(xRadius); 1300 SkScalar yRadRecip = SkScalarInvert(yRadius); 1301 SkScalar xInnerRadRecip = SkScalarInvert(ellipse.fInnerXRadius); 1302 SkScalar yInnerRadRecip = SkScalarInvert(ellipse.fInnerYRadius); 1303 1304 // fOffsets are expanded from xyRadii to include the half-pixel antialiasing width. 1305 SkScalar xMaxOffset = xRadius + SK_ScalarHalf; 1306 SkScalar yMaxOffset = yRadius + SK_ScalarHalf; 1307 1308 // The inner radius in the vertex data must be specified in normalized space. 1309 verts[0].fPos = SkPoint::Make(ellipse.fDevBounds.fLeft, ellipse.fDevBounds.fTop); 1310 verts[0].fColor = color; 1311 verts[0].fOffset = SkPoint::Make(-xMaxOffset, -yMaxOffset); 1312 verts[0].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); 1313 verts[0].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); 1314 1315 verts[1].fPos = SkPoint::Make(ellipse.fDevBounds.fLeft, ellipse.fDevBounds.fBottom); 1316 verts[1].fColor = color; 1317 verts[1].fOffset = SkPoint::Make(-xMaxOffset, yMaxOffset); 1318 verts[1].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); 1319 verts[1].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); 1320 1321 verts[2].fPos = SkPoint::Make(ellipse.fDevBounds.fRight, ellipse.fDevBounds.fBottom); 1322 verts[2].fColor = color; 1323 verts[2].fOffset = SkPoint::Make(xMaxOffset, yMaxOffset); 1324 verts[2].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); 1325 verts[2].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); 1326 1327 verts[3].fPos = SkPoint::Make(ellipse.fDevBounds.fRight, ellipse.fDevBounds.fTop); 1328 verts[3].fColor = color; 1329 verts[3].fOffset = SkPoint::Make(xMaxOffset, -yMaxOffset); 1330 verts[3].fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); 1331 verts[3].fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); 1332 1333 verts += kVerticesPerQuad; 1334 } 1335 helper.recordDraw(target, gp.get(), fHelper.makePipeline(target)); 1336 } 1337 1338 bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override { 1339 EllipseOp* that = t->cast<EllipseOp>(); 1340 1341 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 1342 return false; 1343 } 1344 1345 if (fStroked != that->fStroked) { 1346 return false; 1347 } 1348 1349 if (fHelper.usesLocalCoords() && 1350 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) { 1351 return false; 1352 } 1353 1354 fEllipses.push_back_n(that->fEllipses.count(), that->fEllipses.begin()); 1355 this->joinBounds(*that); 1356 return true; 1357 } 1358 1359 struct Ellipse { 1360 GrColor fColor; 1361 SkScalar fXRadius; 1362 SkScalar fYRadius; 1363 SkScalar fInnerXRadius; 1364 SkScalar fInnerYRadius; 1365 SkRect fDevBounds; 1366 }; 1367 1368 SkMatrix fViewMatrixIfUsingLocalCoords; 1369 Helper fHelper; 1370 bool fStroked; 1371 SkSTArray<1, Ellipse, true> fEllipses; 1372 1373 typedef GrMeshDrawOp INHERITED; 1374 }; 1375 1376 ///////////////////////////////////////////////////////////////////////////////////////////////// 1377 1378 class DIEllipseOp : public GrMeshDrawOp { 1379 private: 1380 using Helper = GrSimpleMeshDrawOpHelper; 1381 1382 struct DeviceSpaceParams { 1383 SkPoint fCenter; 1384 SkScalar fXRadius; 1385 SkScalar fYRadius; 1386 SkScalar fInnerXRadius; 1387 SkScalar fInnerYRadius; 1388 DIEllipseStyle fStyle; 1389 }; 1390 1391 public: 1392 DEFINE_OP_CLASS_ID 1393 1394 static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, const SkMatrix& viewMatrix, 1395 const SkRect& ellipse, const SkStrokeRec& stroke) { 1396 DeviceSpaceParams params; 1397 params.fCenter = SkPoint::Make(ellipse.centerX(), ellipse.centerY()); 1398 params.fXRadius = SkScalarHalf(ellipse.width()); 1399 params.fYRadius = SkScalarHalf(ellipse.height()); 1400 1401 SkStrokeRec::Style style = stroke.getStyle(); 1402 params.fStyle = (SkStrokeRec::kStroke_Style == style) 1403 ? DIEllipseStyle::kStroke 1404 : (SkStrokeRec::kHairline_Style == style) 1405 ? DIEllipseStyle::kHairline 1406 : DIEllipseStyle::kFill; 1407 1408 params.fInnerXRadius = 0; 1409 params.fInnerYRadius = 0; 1410 if (SkStrokeRec::kFill_Style != style && SkStrokeRec::kHairline_Style != style) { 1411 SkScalar strokeWidth = stroke.getWidth(); 1412 1413 if (SkScalarNearlyZero(strokeWidth)) { 1414 strokeWidth = SK_ScalarHalf; 1415 } else { 1416 strokeWidth *= SK_ScalarHalf; 1417 } 1418 1419 // we only handle thick strokes for near-circular ellipses 1420 if (strokeWidth > SK_ScalarHalf && 1421 (SK_ScalarHalf * params.fXRadius > params.fYRadius || 1422 SK_ScalarHalf * params.fYRadius > params.fXRadius)) { 1423 return nullptr; 1424 } 1425 1426 // we don't handle it if curvature of the stroke is less than curvature of the ellipse 1427 if (strokeWidth * (params.fYRadius * params.fYRadius) < 1428 (strokeWidth * strokeWidth) * params.fXRadius) { 1429 return nullptr; 1430 } 1431 if (strokeWidth * (params.fXRadius * params.fXRadius) < 1432 (strokeWidth * strokeWidth) * params.fYRadius) { 1433 return nullptr; 1434 } 1435 1436 // set inner radius (if needed) 1437 if (SkStrokeRec::kStroke_Style == style) { 1438 params.fInnerXRadius = params.fXRadius - strokeWidth; 1439 params.fInnerYRadius = params.fYRadius - strokeWidth; 1440 } 1441 1442 params.fXRadius += strokeWidth; 1443 params.fYRadius += strokeWidth; 1444 } 1445 if (DIEllipseStyle::kStroke == params.fStyle && 1446 (params.fInnerXRadius <= 0 || params.fInnerYRadius <= 0)) { 1447 params.fStyle = DIEllipseStyle::kFill; 1448 } 1449 return Helper::FactoryHelper<DIEllipseOp>(std::move(paint), params, viewMatrix); 1450 } 1451 1452 DIEllipseOp(Helper::MakeArgs& helperArgs, GrColor color, const DeviceSpaceParams& params, 1453 const SkMatrix& viewMatrix) 1454 : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) { 1455 // This expands the outer rect so that after CTM we end up with a half-pixel border 1456 SkScalar a = viewMatrix[SkMatrix::kMScaleX]; 1457 SkScalar b = viewMatrix[SkMatrix::kMSkewX]; 1458 SkScalar c = viewMatrix[SkMatrix::kMSkewY]; 1459 SkScalar d = viewMatrix[SkMatrix::kMScaleY]; 1460 SkScalar geoDx = SK_ScalarHalf / SkScalarSqrt(a * a + c * c); 1461 SkScalar geoDy = SK_ScalarHalf / SkScalarSqrt(b * b + d * d); 1462 1463 fEllipses.emplace_back( 1464 Ellipse{viewMatrix, color, params.fXRadius, params.fYRadius, params.fInnerXRadius, 1465 params.fInnerYRadius, geoDx, geoDy, params.fStyle, 1466 SkRect::MakeLTRB(params.fCenter.fX - params.fXRadius - geoDx, 1467 params.fCenter.fY - params.fYRadius - geoDy, 1468 params.fCenter.fX + params.fXRadius + geoDx, 1469 params.fCenter.fY + params.fYRadius + geoDy)}); 1470 this->setTransformedBounds(fEllipses[0].fBounds, viewMatrix, HasAABloat::kYes, 1471 IsZeroArea::kNo); 1472 } 1473 1474 const char* name() const override { return "DIEllipseOp"; } 1475 1476 SkString dumpInfo() const override { 1477 SkString string; 1478 for (const auto& geo : fEllipses) { 1479 string.appendf( 1480 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], XRad: %.2f, " 1481 "YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f, GeoDX: %.2f, " 1482 "GeoDY: %.2f\n", 1483 geo.fColor, geo.fBounds.fLeft, geo.fBounds.fTop, geo.fBounds.fRight, 1484 geo.fBounds.fBottom, geo.fXRadius, geo.fYRadius, geo.fInnerXRadius, 1485 geo.fInnerYRadius, geo.fGeoDx, geo.fGeoDy); 1486 } 1487 string += fHelper.dumpInfo(); 1488 string += INHERITED::dumpInfo(); 1489 return string; 1490 } 1491 1492 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override { 1493 GrColor* color = &fEllipses.front().fColor; 1494 return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel, 1495 color); 1496 } 1497 1498 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 1499 1500 private: 1501 void onPrepareDraws(Target* target) const override { 1502 // Setup geometry processor 1503 sk_sp<GrGeometryProcessor> gp( 1504 new DIEllipseGeometryProcessor(this->viewMatrix(), this->style())); 1505 1506 size_t vertexStride = gp->getVertexStride(); 1507 SkASSERT(vertexStride == sizeof(DIEllipseVertex)); 1508 QuadHelper helper; 1509 DIEllipseVertex* verts = reinterpret_cast<DIEllipseVertex*>( 1510 helper.init(target, vertexStride, fEllipses.count())); 1511 if (!verts) { 1512 return; 1513 } 1514 1515 for (const auto& ellipse : fEllipses) { 1516 GrColor color = ellipse.fColor; 1517 SkScalar xRadius = ellipse.fXRadius; 1518 SkScalar yRadius = ellipse.fYRadius; 1519 1520 const SkRect& bounds = ellipse.fBounds; 1521 1522 // This adjusts the "radius" to include the half-pixel border 1523 SkScalar offsetDx = ellipse.fGeoDx / xRadius; 1524 SkScalar offsetDy = ellipse.fGeoDy / yRadius; 1525 1526 SkScalar innerRatioX = xRadius / ellipse.fInnerXRadius; 1527 SkScalar innerRatioY = yRadius / ellipse.fInnerYRadius; 1528 1529 verts[0].fPos = SkPoint::Make(bounds.fLeft, bounds.fTop); 1530 verts[0].fColor = color; 1531 verts[0].fOuterOffset = SkPoint::Make(-1.0f - offsetDx, -1.0f - offsetDy); 1532 verts[0].fInnerOffset = SkPoint::Make(-innerRatioX - offsetDx, -innerRatioY - offsetDy); 1533 1534 verts[1].fPos = SkPoint::Make(bounds.fLeft, bounds.fBottom); 1535 verts[1].fColor = color; 1536 verts[1].fOuterOffset = SkPoint::Make(-1.0f - offsetDx, 1.0f + offsetDy); 1537 verts[1].fInnerOffset = SkPoint::Make(-innerRatioX - offsetDx, innerRatioY + offsetDy); 1538 1539 verts[2].fPos = SkPoint::Make(bounds.fRight, bounds.fBottom); 1540 verts[2].fColor = color; 1541 verts[2].fOuterOffset = SkPoint::Make(1.0f + offsetDx, 1.0f + offsetDy); 1542 verts[2].fInnerOffset = SkPoint::Make(innerRatioX + offsetDx, innerRatioY + offsetDy); 1543 1544 verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fTop); 1545 verts[3].fColor = color; 1546 verts[3].fOuterOffset = SkPoint::Make(1.0f + offsetDx, -1.0f - offsetDy); 1547 verts[3].fInnerOffset = SkPoint::Make(innerRatioX + offsetDx, -innerRatioY - offsetDy); 1548 1549 verts += kVerticesPerQuad; 1550 } 1551 helper.recordDraw(target, gp.get(), fHelper.makePipeline(target)); 1552 } 1553 1554 bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override { 1555 DIEllipseOp* that = t->cast<DIEllipseOp>(); 1556 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 1557 return false; 1558 } 1559 1560 if (this->style() != that->style()) { 1561 return false; 1562 } 1563 1564 // TODO rewrite to allow positioning on CPU 1565 if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) { 1566 return false; 1567 } 1568 1569 fEllipses.push_back_n(that->fEllipses.count(), that->fEllipses.begin()); 1570 this->joinBounds(*that); 1571 return true; 1572 } 1573 1574 const SkMatrix& viewMatrix() const { return fEllipses[0].fViewMatrix; } 1575 DIEllipseStyle style() const { return fEllipses[0].fStyle; } 1576 1577 struct Ellipse { 1578 SkMatrix fViewMatrix; 1579 GrColor fColor; 1580 SkScalar fXRadius; 1581 SkScalar fYRadius; 1582 SkScalar fInnerXRadius; 1583 SkScalar fInnerYRadius; 1584 SkScalar fGeoDx; 1585 SkScalar fGeoDy; 1586 DIEllipseStyle fStyle; 1587 SkRect fBounds; 1588 }; 1589 1590 Helper fHelper; 1591 SkSTArray<1, Ellipse, true> fEllipses; 1592 1593 typedef GrMeshDrawOp INHERITED; 1594 }; 1595 1596 /////////////////////////////////////////////////////////////////////////////// 1597 1598 // We have three possible cases for geometry for a roundrect. 1599 // 1600 // In the case of a normal fill or a stroke, we draw the roundrect as a 9-patch: 1601 // ____________ 1602 // |_|________|_| 1603 // | | | | 1604 // | | | | 1605 // | | | | 1606 // |_|________|_| 1607 // |_|________|_| 1608 // 1609 // For strokes, we don't draw the center quad. 1610 // 1611 // For circular roundrects, in the case where the stroke width is greater than twice 1612 // the corner radius (overstroke), we add additional geometry to mark out the rectangle 1613 // in the center. The shared vertices are duplicated so we can set a different outer radius 1614 // for the fill calculation. 1615 // ____________ 1616 // |_|________|_| 1617 // | |\ ____ /| | 1618 // | | | | | | 1619 // | | |____| | | 1620 // |_|/______\|_| 1621 // |_|________|_| 1622 // 1623 // We don't draw the center quad from the fill rect in this case. 1624 // 1625 // For filled rrects that need to provide a distance vector we resuse the overstroke 1626 // geometry but make the inner rect degenerate (either a point or a horizontal or 1627 // vertical line). 1628 1629 static const uint16_t gOverstrokeRRectIndices[] = { 1630 // clang-format off 1631 // overstroke quads 1632 // we place this at the beginning so that we can skip these indices when rendering normally 1633 16, 17, 19, 16, 19, 18, 1634 19, 17, 23, 19, 23, 21, 1635 21, 23, 22, 21, 22, 20, 1636 22, 16, 18, 22, 18, 20, 1637 1638 // corners 1639 0, 1, 5, 0, 5, 4, 1640 2, 3, 7, 2, 7, 6, 1641 8, 9, 13, 8, 13, 12, 1642 10, 11, 15, 10, 15, 14, 1643 1644 // edges 1645 1, 2, 6, 1, 6, 5, 1646 4, 5, 9, 4, 9, 8, 1647 6, 7, 11, 6, 11, 10, 1648 9, 10, 14, 9, 14, 13, 1649 1650 // center 1651 // we place this at the end so that we can ignore these indices when not rendering as filled 1652 5, 6, 10, 5, 10, 9, 1653 // clang-format on 1654 }; 1655 1656 // fill and standard stroke indices skip the overstroke "ring" 1657 static const uint16_t* gStandardRRectIndices = gOverstrokeRRectIndices + 6 * 4; 1658 1659 // overstroke count is arraysize minus the center indices 1660 static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gOverstrokeRRectIndices) - 6; 1661 // fill count skips overstroke indices and includes center 1662 static const int kIndicesPerFillRRect = kIndicesPerOverstrokeRRect - 6 * 4 + 6; 1663 // stroke count is fill count minus center indices 1664 static const int kIndicesPerStrokeRRect = kIndicesPerFillRRect - 6; 1665 static const int kVertsPerStandardRRect = 16; 1666 static const int kVertsPerOverstrokeRRect = 24; 1667 1668 enum RRectType { 1669 kFill_RRectType, 1670 kStroke_RRectType, 1671 kOverstroke_RRectType, 1672 }; 1673 1674 static int rrect_type_to_vert_count(RRectType type) { 1675 switch (type) { 1676 case kFill_RRectType: 1677 case kStroke_RRectType: 1678 return kVertsPerStandardRRect; 1679 case kOverstroke_RRectType: 1680 return kVertsPerOverstrokeRRect; 1681 } 1682 SkFAIL("Invalid type"); 1683 return 0; 1684 } 1685 1686 static int rrect_type_to_index_count(RRectType type) { 1687 switch (type) { 1688 case kFill_RRectType: 1689 return kIndicesPerFillRRect; 1690 case kStroke_RRectType: 1691 return kIndicesPerStrokeRRect; 1692 case kOverstroke_RRectType: 1693 return kIndicesPerOverstrokeRRect; 1694 } 1695 SkFAIL("Invalid type"); 1696 return 0; 1697 } 1698 1699 static const uint16_t* rrect_type_to_indices(RRectType type) { 1700 switch (type) { 1701 case kFill_RRectType: 1702 case kStroke_RRectType: 1703 return gStandardRRectIndices; 1704 case kOverstroke_RRectType: 1705 return gOverstrokeRRectIndices; 1706 } 1707 SkFAIL("Invalid type"); 1708 return 0; 1709 } 1710 1711 /////////////////////////////////////////////////////////////////////////////////////////////////// 1712 1713 // For distance computations in the interior of filled rrects we: 1714 // 1715 // add a interior degenerate (point or line) rect 1716 // each vertex of that rect gets -outerRad as its radius 1717 // this makes the computation of the distance to the outer edge be negative 1718 // negative values are caught and then handled differently in the GP's onEmitCode 1719 // each vertex is also given the normalized x & y distance from the interior rect's edge 1720 // the GP takes the min of those depths +1 to get the normalized distance to the outer edge 1721 1722 class CircularRRectOp : public GrMeshDrawOp { 1723 private: 1724 using Helper = GrSimpleMeshDrawOpHelper; 1725 1726 public: 1727 DEFINE_OP_CLASS_ID 1728 1729 // A devStrokeWidth <= 0 indicates a fill only. If devStrokeWidth > 0 then strokeOnly indicates 1730 // whether the rrect is only stroked or stroked and filled. 1731 static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, const SkMatrix& viewMatrix, 1732 const SkRect& devRect, float devRadius, 1733 float devStrokeWidth, bool strokeOnly) { 1734 return Helper::FactoryHelper<CircularRRectOp>(std::move(paint), viewMatrix, devRect, 1735 devRadius, devStrokeWidth, strokeOnly); 1736 } 1737 CircularRRectOp(Helper::MakeArgs& helperArgs, GrColor color, const SkMatrix& viewMatrix, 1738 const SkRect& devRect, float devRadius, float devStrokeWidth, bool strokeOnly) 1739 : INHERITED(ClassID()) 1740 , fViewMatrixIfUsingLocalCoords(viewMatrix) 1741 , fHelper(helperArgs, GrAAType::kCoverage) { 1742 SkRect bounds = devRect; 1743 SkASSERT(!(devStrokeWidth <= 0 && strokeOnly)); 1744 SkScalar innerRadius = 0.0f; 1745 SkScalar outerRadius = devRadius; 1746 SkScalar halfWidth = 0; 1747 RRectType type = kFill_RRectType; 1748 if (devStrokeWidth > 0) { 1749 if (SkScalarNearlyZero(devStrokeWidth)) { 1750 halfWidth = SK_ScalarHalf; 1751 } else { 1752 halfWidth = SkScalarHalf(devStrokeWidth); 1753 } 1754 1755 if (strokeOnly) { 1756 // Outset stroke by 1/4 pixel 1757 devStrokeWidth += 0.25f; 1758 // If stroke is greater than width or height, this is still a fill 1759 // Otherwise we compute stroke params 1760 if (devStrokeWidth <= devRect.width() && devStrokeWidth <= devRect.height()) { 1761 innerRadius = devRadius - halfWidth; 1762 type = (innerRadius >= 0) ? kStroke_RRectType : kOverstroke_RRectType; 1763 } 1764 } 1765 outerRadius += halfWidth; 1766 bounds.outset(halfWidth, halfWidth); 1767 } 1768 1769 // The radii are outset for two reasons. First, it allows the shader to simply perform 1770 // simpler computation because the computed alpha is zero, rather than 50%, at the radius. 1771 // Second, the outer radius is used to compute the verts of the bounding box that is 1772 // rendered and the outset ensures the box will cover all partially covered by the rrect 1773 // corners. 1774 outerRadius += SK_ScalarHalf; 1775 innerRadius -= SK_ScalarHalf; 1776 1777 this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo); 1778 1779 // Expand the rect for aa to generate correct vertices. 1780 bounds.outset(SK_ScalarHalf, SK_ScalarHalf); 1781 1782 fRRects.emplace_back(RRect{color, innerRadius, outerRadius, bounds, type}); 1783 fVertCount = rrect_type_to_vert_count(type); 1784 fIndexCount = rrect_type_to_index_count(type); 1785 fAllFill = (kFill_RRectType == type); 1786 } 1787 1788 const char* name() const override { return "CircularRRectOp"; } 1789 1790 SkString dumpInfo() const override { 1791 SkString string; 1792 for (int i = 0; i < fRRects.count(); ++i) { 1793 string.appendf( 1794 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]," 1795 "InnerRad: %.2f, OuterRad: %.2f\n", 1796 fRRects[i].fColor, fRRects[i].fDevBounds.fLeft, fRRects[i].fDevBounds.fTop, 1797 fRRects[i].fDevBounds.fRight, fRRects[i].fDevBounds.fBottom, 1798 fRRects[i].fInnerRadius, fRRects[i].fOuterRadius); 1799 } 1800 string += fHelper.dumpInfo(); 1801 string += INHERITED::dumpInfo(); 1802 return string; 1803 } 1804 1805 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override { 1806 GrColor* color = &fRRects.front().fColor; 1807 return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel, 1808 color); 1809 } 1810 1811 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 1812 1813 private: 1814 struct CircleVertex { 1815 SkPoint fPos; 1816 GrColor fColor; 1817 SkPoint fOffset; 1818 SkScalar fOuterRadius; 1819 SkScalar fInnerRadius; 1820 // No half plane, we don't use it here. 1821 }; 1822 1823 static void FillInOverstrokeVerts(CircleVertex** verts, const SkRect& bounds, SkScalar smInset, 1824 SkScalar bigInset, SkScalar xOffset, SkScalar outerRadius, 1825 SkScalar innerRadius, GrColor color) { 1826 SkASSERT(smInset < bigInset); 1827 1828 // TL 1829 (*verts)->fPos = SkPoint::Make(bounds.fLeft + smInset, bounds.fTop + smInset); 1830 (*verts)->fColor = color; 1831 (*verts)->fOffset = SkPoint::Make(xOffset, 0); 1832 (*verts)->fOuterRadius = outerRadius; 1833 (*verts)->fInnerRadius = innerRadius; 1834 (*verts)++; 1835 1836 // TR 1837 (*verts)->fPos = SkPoint::Make(bounds.fRight - smInset, bounds.fTop + smInset); 1838 (*verts)->fColor = color; 1839 (*verts)->fOffset = SkPoint::Make(xOffset, 0); 1840 (*verts)->fOuterRadius = outerRadius; 1841 (*verts)->fInnerRadius = innerRadius; 1842 (*verts)++; 1843 1844 (*verts)->fPos = SkPoint::Make(bounds.fLeft + bigInset, bounds.fTop + bigInset); 1845 (*verts)->fColor = color; 1846 (*verts)->fOffset = SkPoint::Make(0, 0); 1847 (*verts)->fOuterRadius = outerRadius; 1848 (*verts)->fInnerRadius = innerRadius; 1849 (*verts)++; 1850 1851 (*verts)->fPos = SkPoint::Make(bounds.fRight - bigInset, bounds.fTop + bigInset); 1852 (*verts)->fColor = color; 1853 (*verts)->fOffset = SkPoint::Make(0, 0); 1854 (*verts)->fOuterRadius = outerRadius; 1855 (*verts)->fInnerRadius = innerRadius; 1856 (*verts)++; 1857 1858 (*verts)->fPos = SkPoint::Make(bounds.fLeft + bigInset, bounds.fBottom - bigInset); 1859 (*verts)->fColor = color; 1860 (*verts)->fOffset = SkPoint::Make(0, 0); 1861 (*verts)->fOuterRadius = outerRadius; 1862 (*verts)->fInnerRadius = innerRadius; 1863 (*verts)++; 1864 1865 (*verts)->fPos = SkPoint::Make(bounds.fRight - bigInset, bounds.fBottom - bigInset); 1866 (*verts)->fColor = color; 1867 (*verts)->fOffset = SkPoint::Make(0, 0); 1868 (*verts)->fOuterRadius = outerRadius; 1869 (*verts)->fInnerRadius = innerRadius; 1870 (*verts)++; 1871 1872 // BL 1873 (*verts)->fPos = SkPoint::Make(bounds.fLeft + smInset, bounds.fBottom - smInset); 1874 (*verts)->fColor = color; 1875 (*verts)->fOffset = SkPoint::Make(xOffset, 0); 1876 (*verts)->fOuterRadius = outerRadius; 1877 (*verts)->fInnerRadius = innerRadius; 1878 (*verts)++; 1879 1880 // BR 1881 (*verts)->fPos = SkPoint::Make(bounds.fRight - smInset, bounds.fBottom - smInset); 1882 (*verts)->fColor = color; 1883 (*verts)->fOffset = SkPoint::Make(xOffset, 0); 1884 (*verts)->fOuterRadius = outerRadius; 1885 (*verts)->fInnerRadius = innerRadius; 1886 (*verts)++; 1887 } 1888 1889 void onPrepareDraws(Target* target) const override { 1890 // Invert the view matrix as a local matrix (if any other processors require coords). 1891 SkMatrix localMatrix; 1892 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { 1893 return; 1894 } 1895 1896 // Setup geometry processor 1897 sk_sp<GrGeometryProcessor> gp( 1898 new CircleGeometryProcessor(!fAllFill, false, false, false, localMatrix)); 1899 1900 size_t vertexStride = gp->getVertexStride(); 1901 SkASSERT(sizeof(CircleVertex) == vertexStride); 1902 1903 const GrBuffer* vertexBuffer; 1904 int firstVertex; 1905 1906 CircleVertex* verts = (CircleVertex*)target->makeVertexSpace(vertexStride, fVertCount, 1907 &vertexBuffer, &firstVertex); 1908 if (!verts) { 1909 SkDebugf("Could not allocate vertices\n"); 1910 return; 1911 } 1912 1913 const GrBuffer* indexBuffer = nullptr; 1914 int firstIndex = 0; 1915 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex); 1916 if (!indices) { 1917 SkDebugf("Could not allocate indices\n"); 1918 return; 1919 } 1920 1921 int currStartVertex = 0; 1922 for (const auto& rrect : fRRects) { 1923 GrColor color = rrect.fColor; 1924 SkScalar outerRadius = rrect.fOuterRadius; 1925 const SkRect& bounds = rrect.fDevBounds; 1926 1927 SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + outerRadius, 1928 bounds.fBottom - outerRadius, bounds.fBottom}; 1929 1930 SkScalar yOuterRadii[4] = {-1, 0, 0, 1}; 1931 // The inner radius in the vertex data must be specified in normalized space. 1932 // For fills, specifying -1/outerRadius guarantees an alpha of 1.0 at the inner radius. 1933 SkScalar innerRadius = rrect.fType != kFill_RRectType 1934 ? rrect.fInnerRadius / rrect.fOuterRadius 1935 : -1.0f / rrect.fOuterRadius; 1936 for (int i = 0; i < 4; ++i) { 1937 verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]); 1938 verts->fColor = color; 1939 verts->fOffset = SkPoint::Make(-1, yOuterRadii[i]); 1940 verts->fOuterRadius = outerRadius; 1941 verts->fInnerRadius = innerRadius; 1942 verts++; 1943 1944 verts->fPos = SkPoint::Make(bounds.fLeft + outerRadius, yCoords[i]); 1945 verts->fColor = color; 1946 verts->fOffset = SkPoint::Make(0, yOuterRadii[i]); 1947 verts->fOuterRadius = outerRadius; 1948 verts->fInnerRadius = innerRadius; 1949 verts++; 1950 1951 verts->fPos = SkPoint::Make(bounds.fRight - outerRadius, yCoords[i]); 1952 verts->fColor = color; 1953 verts->fOffset = SkPoint::Make(0, yOuterRadii[i]); 1954 verts->fOuterRadius = outerRadius; 1955 verts->fInnerRadius = innerRadius; 1956 verts++; 1957 1958 verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]); 1959 verts->fColor = color; 1960 verts->fOffset = SkPoint::Make(1, yOuterRadii[i]); 1961 verts->fOuterRadius = outerRadius; 1962 verts->fInnerRadius = innerRadius; 1963 verts++; 1964 } 1965 // Add the additional vertices for overstroked rrects. 1966 // Effectively this is an additional stroked rrect, with its 1967 // outer radius = outerRadius - innerRadius, and inner radius = 0. 1968 // This will give us correct AA in the center and the correct 1969 // distance to the outer edge. 1970 // 1971 // Also, the outer offset is a constant vector pointing to the right, which 1972 // guarantees that the distance value along the outer rectangle is constant. 1973 if (kOverstroke_RRectType == rrect.fType) { 1974 SkASSERT(rrect.fInnerRadius <= 0.0f); 1975 1976 SkScalar overstrokeOuterRadius = outerRadius - rrect.fInnerRadius; 1977 // this is the normalized distance from the outer rectangle of this 1978 // geometry to the outer edge 1979 SkScalar maxOffset = -rrect.fInnerRadius / overstrokeOuterRadius; 1980 1981 FillInOverstrokeVerts(&verts, bounds, outerRadius, overstrokeOuterRadius, maxOffset, 1982 overstrokeOuterRadius, 0.0f, rrect.fColor); 1983 } 1984 1985 const uint16_t* primIndices = rrect_type_to_indices(rrect.fType); 1986 const int primIndexCount = rrect_type_to_index_count(rrect.fType); 1987 for (int i = 0; i < primIndexCount; ++i) { 1988 *indices++ = primIndices[i] + currStartVertex; 1989 } 1990 1991 currStartVertex += rrect_type_to_vert_count(rrect.fType); 1992 } 1993 1994 GrMesh mesh(GrPrimitiveType::kTriangles); 1995 mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertCount - 1); 1996 mesh.setVertexData(vertexBuffer, firstVertex); 1997 target->draw(gp.get(), fHelper.makePipeline(target), mesh); 1998 } 1999 2000 bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override { 2001 CircularRRectOp* that = t->cast<CircularRRectOp>(); 2002 2003 // can only represent 65535 unique vertices with 16-bit indices 2004 if (fVertCount + that->fVertCount > 65536) { 2005 return false; 2006 } 2007 2008 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 2009 return false; 2010 } 2011 2012 if (fHelper.usesLocalCoords() && 2013 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) { 2014 return false; 2015 } 2016 2017 fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin()); 2018 this->joinBounds(*that); 2019 fVertCount += that->fVertCount; 2020 fIndexCount += that->fIndexCount; 2021 fAllFill = fAllFill && that->fAllFill; 2022 return true; 2023 } 2024 2025 struct RRect { 2026 GrColor fColor; 2027 SkScalar fInnerRadius; 2028 SkScalar fOuterRadius; 2029 SkRect fDevBounds; 2030 RRectType fType; 2031 }; 2032 2033 SkMatrix fViewMatrixIfUsingLocalCoords; 2034 Helper fHelper; 2035 int fVertCount; 2036 int fIndexCount; 2037 bool fAllFill; 2038 SkSTArray<1, RRect, true> fRRects; 2039 2040 typedef GrMeshDrawOp INHERITED; 2041 }; 2042 2043 static const int kNumRRectsInIndexBuffer = 256; 2044 2045 GR_DECLARE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey); 2046 GR_DECLARE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey); 2047 static const GrBuffer* ref_rrect_index_buffer(RRectType type, 2048 GrResourceProvider* resourceProvider) { 2049 GR_DEFINE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey); 2050 GR_DEFINE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey); 2051 switch (type) { 2052 case kFill_RRectType: 2053 return resourceProvider->findOrCreatePatternedIndexBuffer( 2054 gStandardRRectIndices, kIndicesPerFillRRect, kNumRRectsInIndexBuffer, 2055 kVertsPerStandardRRect, gRRectOnlyIndexBufferKey); 2056 case kStroke_RRectType: 2057 return resourceProvider->findOrCreatePatternedIndexBuffer( 2058 gStandardRRectIndices, kIndicesPerStrokeRRect, kNumRRectsInIndexBuffer, 2059 kVertsPerStandardRRect, gStrokeRRectOnlyIndexBufferKey); 2060 default: 2061 SkASSERT(false); 2062 return nullptr; 2063 }; 2064 } 2065 2066 class EllipticalRRectOp : public GrMeshDrawOp { 2067 private: 2068 using Helper = GrSimpleMeshDrawOpHelper; 2069 2070 public: 2071 DEFINE_OP_CLASS_ID 2072 2073 // If devStrokeWidths values are <= 0 indicates then fill only. Otherwise, strokeOnly indicates 2074 // whether the rrect is only stroked or stroked and filled. 2075 static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, const SkMatrix& viewMatrix, 2076 const SkRect& devRect, float devXRadius, float devYRadius, 2077 SkVector devStrokeWidths, bool strokeOnly) { 2078 SkASSERT(devXRadius > 0.5); 2079 SkASSERT(devYRadius > 0.5); 2080 SkASSERT((devStrokeWidths.fX > 0) == (devStrokeWidths.fY > 0)); 2081 SkASSERT(!(strokeOnly && devStrokeWidths.fX <= 0)); 2082 if (devStrokeWidths.fX > 0) { 2083 if (SkScalarNearlyZero(devStrokeWidths.length())) { 2084 devStrokeWidths.set(SK_ScalarHalf, SK_ScalarHalf); 2085 } else { 2086 devStrokeWidths.scale(SK_ScalarHalf); 2087 } 2088 2089 // we only handle thick strokes for near-circular ellipses 2090 if (devStrokeWidths.length() > SK_ScalarHalf && 2091 (SK_ScalarHalf * devXRadius > devYRadius || 2092 SK_ScalarHalf * devYRadius > devXRadius)) { 2093 return nullptr; 2094 } 2095 2096 // we don't handle it if curvature of the stroke is less than curvature of the ellipse 2097 if (devStrokeWidths.fX * (devYRadius * devYRadius) < 2098 (devStrokeWidths.fY * devStrokeWidths.fY) * devXRadius) { 2099 return nullptr; 2100 } 2101 if (devStrokeWidths.fY * (devXRadius * devXRadius) < 2102 (devStrokeWidths.fX * devStrokeWidths.fX) * devYRadius) { 2103 return nullptr; 2104 } 2105 } 2106 return Helper::FactoryHelper<EllipticalRRectOp>(std::move(paint), viewMatrix, devRect, 2107 devXRadius, devYRadius, devStrokeWidths, 2108 strokeOnly); 2109 } 2110 2111 EllipticalRRectOp(Helper::MakeArgs helperArgs, GrColor color, const SkMatrix& viewMatrix, 2112 const SkRect& devRect, float devXRadius, float devYRadius, 2113 SkVector devStrokeHalfWidths, bool strokeOnly) 2114 : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) { 2115 SkScalar innerXRadius = 0.0f; 2116 SkScalar innerYRadius = 0.0f; 2117 SkRect bounds = devRect; 2118 bool stroked = false; 2119 if (devStrokeHalfWidths.fX > 0) { 2120 // this is legit only if scale & translation (which should be the case at the moment) 2121 if (strokeOnly) { 2122 innerXRadius = devXRadius - devStrokeHalfWidths.fX; 2123 innerYRadius = devYRadius - devStrokeHalfWidths.fY; 2124 stroked = (innerXRadius >= 0 && innerYRadius >= 0); 2125 } 2126 2127 devXRadius += devStrokeHalfWidths.fX; 2128 devYRadius += devStrokeHalfWidths.fY; 2129 bounds.outset(devStrokeHalfWidths.fX, devStrokeHalfWidths.fY); 2130 } 2131 2132 fStroked = stroked; 2133 fViewMatrixIfUsingLocalCoords = viewMatrix; 2134 this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo); 2135 // Expand the rect for aa in order to generate the correct vertices. 2136 bounds.outset(SK_ScalarHalf, SK_ScalarHalf); 2137 fRRects.emplace_back( 2138 RRect{color, devXRadius, devYRadius, innerXRadius, innerYRadius, bounds}); 2139 } 2140 2141 const char* name() const override { return "EllipticalRRectOp"; } 2142 2143 SkString dumpInfo() const override { 2144 SkString string; 2145 string.appendf("Stroked: %d\n", fStroked); 2146 for (const auto& geo : fRRects) { 2147 string.appendf( 2148 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], " 2149 "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n", 2150 geo.fColor, geo.fDevBounds.fLeft, geo.fDevBounds.fTop, geo.fDevBounds.fRight, 2151 geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius, geo.fInnerXRadius, 2152 geo.fInnerYRadius); 2153 } 2154 string += fHelper.dumpInfo(); 2155 string += INHERITED::dumpInfo(); 2156 return string; 2157 } 2158 2159 RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override { 2160 GrColor* color = &fRRects.front().fColor; 2161 return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kSingleChannel, 2162 color); 2163 } 2164 2165 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 2166 2167 private: 2168 void onPrepareDraws(Target* target) const override { 2169 SkMatrix localMatrix; 2170 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { 2171 return; 2172 } 2173 2174 // Setup geometry processor 2175 sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, localMatrix)); 2176 2177 size_t vertexStride = gp->getVertexStride(); 2178 SkASSERT(vertexStride == sizeof(EllipseVertex)); 2179 2180 // drop out the middle quad if we're stroked 2181 int indicesPerInstance = fStroked ? kIndicesPerStrokeRRect : kIndicesPerFillRRect; 2182 sk_sp<const GrBuffer> indexBuffer(ref_rrect_index_buffer( 2183 fStroked ? kStroke_RRectType : kFill_RRectType, target->resourceProvider())); 2184 2185 PatternHelper helper(GrPrimitiveType::kTriangles); 2186 EllipseVertex* verts = reinterpret_cast<EllipseVertex*>( 2187 helper.init(target, vertexStride, indexBuffer.get(), kVertsPerStandardRRect, 2188 indicesPerInstance, fRRects.count())); 2189 if (!verts || !indexBuffer) { 2190 SkDebugf("Could not allocate vertices\n"); 2191 return; 2192 } 2193 2194 for (const auto& rrect : fRRects) { 2195 GrColor color = rrect.fColor; 2196 // Compute the reciprocals of the radii here to save time in the shader 2197 SkScalar xRadRecip = SkScalarInvert(rrect.fXRadius); 2198 SkScalar yRadRecip = SkScalarInvert(rrect.fYRadius); 2199 SkScalar xInnerRadRecip = SkScalarInvert(rrect.fInnerXRadius); 2200 SkScalar yInnerRadRecip = SkScalarInvert(rrect.fInnerYRadius); 2201 2202 // Extend the radii out half a pixel to antialias. 2203 SkScalar xOuterRadius = rrect.fXRadius + SK_ScalarHalf; 2204 SkScalar yOuterRadius = rrect.fYRadius + SK_ScalarHalf; 2205 2206 const SkRect& bounds = rrect.fDevBounds; 2207 2208 SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + yOuterRadius, 2209 bounds.fBottom - yOuterRadius, bounds.fBottom}; 2210 SkScalar yOuterOffsets[4] = {yOuterRadius, 2211 SK_ScalarNearlyZero, // we're using inversesqrt() in 2212 // shader, so can't be exactly 0 2213 SK_ScalarNearlyZero, yOuterRadius}; 2214 2215 for (int i = 0; i < 4; ++i) { 2216 verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]); 2217 verts->fColor = color; 2218 verts->fOffset = SkPoint::Make(xOuterRadius, yOuterOffsets[i]); 2219 verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); 2220 verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); 2221 verts++; 2222 2223 verts->fPos = SkPoint::Make(bounds.fLeft + xOuterRadius, yCoords[i]); 2224 verts->fColor = color; 2225 verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i]); 2226 verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); 2227 verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); 2228 verts++; 2229 2230 verts->fPos = SkPoint::Make(bounds.fRight - xOuterRadius, yCoords[i]); 2231 verts->fColor = color; 2232 verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i]); 2233 verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); 2234 verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); 2235 verts++; 2236 2237 verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]); 2238 verts->fColor = color; 2239 verts->fOffset = SkPoint::Make(xOuterRadius, yOuterOffsets[i]); 2240 verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip); 2241 verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip); 2242 verts++; 2243 } 2244 } 2245 helper.recordDraw(target, gp.get(), fHelper.makePipeline(target)); 2246 } 2247 2248 bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override { 2249 EllipticalRRectOp* that = t->cast<EllipticalRRectOp>(); 2250 2251 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 2252 return false; 2253 } 2254 2255 if (fStroked != that->fStroked) { 2256 return false; 2257 } 2258 2259 if (fHelper.usesLocalCoords() && 2260 !fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) { 2261 return false; 2262 } 2263 2264 fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin()); 2265 this->joinBounds(*that); 2266 return true; 2267 } 2268 2269 struct RRect { 2270 GrColor fColor; 2271 SkScalar fXRadius; 2272 SkScalar fYRadius; 2273 SkScalar fInnerXRadius; 2274 SkScalar fInnerYRadius; 2275 SkRect fDevBounds; 2276 }; 2277 2278 SkMatrix fViewMatrixIfUsingLocalCoords; 2279 Helper fHelper; 2280 bool fStroked; 2281 SkSTArray<1, RRect, true> fRRects; 2282 2283 typedef GrMeshDrawOp INHERITED; 2284 }; 2285 2286 static std::unique_ptr<GrDrawOp> make_rrect_op(GrPaint&& paint, 2287 const SkMatrix& viewMatrix, 2288 const SkRRect& rrect, 2289 const SkStrokeRec& stroke) { 2290 SkASSERT(viewMatrix.rectStaysRect()); 2291 SkASSERT(rrect.isSimple()); 2292 SkASSERT(!rrect.isOval()); 2293 2294 // RRect ops only handle simple, but not too simple, rrects. 2295 // Do any matrix crunching before we reset the draw state for device coords. 2296 const SkRect& rrectBounds = rrect.getBounds(); 2297 SkRect bounds; 2298 viewMatrix.mapRect(&bounds, rrectBounds); 2299 2300 SkVector radii = rrect.getSimpleRadii(); 2301 SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * radii.fX + 2302 viewMatrix[SkMatrix::kMSkewY] * radii.fY); 2303 SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX] * radii.fX + 2304 viewMatrix[SkMatrix::kMScaleY] * radii.fY); 2305 2306 SkStrokeRec::Style style = stroke.getStyle(); 2307 2308 // Do (potentially) anisotropic mapping of stroke. Use -1s to indicate fill-only draws. 2309 SkVector scaledStroke = {-1, -1}; 2310 SkScalar strokeWidth = stroke.getWidth(); 2311 2312 bool isStrokeOnly = 2313 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style; 2314 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style; 2315 2316 bool isCircular = (xRadius == yRadius); 2317 if (hasStroke) { 2318 if (SkStrokeRec::kHairline_Style == style) { 2319 scaledStroke.set(1, 1); 2320 } else { 2321 scaledStroke.fX = SkScalarAbs( 2322 strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY])); 2323 scaledStroke.fY = SkScalarAbs( 2324 strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY])); 2325 } 2326 2327 isCircular = isCircular && scaledStroke.fX == scaledStroke.fY; 2328 // for non-circular rrects, if half of strokewidth is greater than radius, 2329 // we don't handle that right now 2330 if (!isCircular && (SK_ScalarHalf * scaledStroke.fX > xRadius || 2331 SK_ScalarHalf * scaledStroke.fY > yRadius)) { 2332 return nullptr; 2333 } 2334 } 2335 2336 // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on 2337 // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine- 2338 // patch will have fractional coverage. This only matters when the interior is actually filled. 2339 // We could consider falling back to rect rendering here, since a tiny radius is 2340 // indistinguishable from a square corner. 2341 if (!isStrokeOnly && (SK_ScalarHalf > xRadius || SK_ScalarHalf > yRadius)) { 2342 return nullptr; 2343 } 2344 2345 // if the corners are circles, use the circle renderer 2346 if (isCircular) { 2347 return CircularRRectOp::Make(std::move(paint), viewMatrix, bounds, xRadius, scaledStroke.fX, 2348 isStrokeOnly); 2349 // otherwise we use the ellipse renderer 2350 } else { 2351 return EllipticalRRectOp::Make(std::move(paint), viewMatrix, bounds, xRadius, yRadius, 2352 scaledStroke, isStrokeOnly); 2353 } 2354 } 2355 2356 std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrPaint&& paint, 2357 const SkMatrix& viewMatrix, 2358 const SkRRect& rrect, 2359 const SkStrokeRec& stroke, 2360 const GrShaderCaps* shaderCaps) { 2361 if (rrect.isOval()) { 2362 return MakeOvalOp(std::move(paint), viewMatrix, rrect.getBounds(), stroke, shaderCaps); 2363 } 2364 2365 if (!viewMatrix.rectStaysRect() || !rrect.isSimple()) { 2366 return nullptr; 2367 } 2368 2369 return make_rrect_op(std::move(paint), viewMatrix, rrect, stroke); 2370 } 2371 2372 /////////////////////////////////////////////////////////////////////////////// 2373 2374 std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrPaint&& paint, 2375 const SkMatrix& viewMatrix, 2376 const SkRect& oval, 2377 const SkStrokeRec& stroke, 2378 const GrShaderCaps* shaderCaps) { 2379 // we can draw circles 2380 SkScalar width = oval.width(); 2381 if (width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) && 2382 circle_stays_circle(viewMatrix)) { 2383 SkPoint center = {oval.centerX(), oval.centerY()}; 2384 return CircleOp::Make(std::move(paint), viewMatrix, center, width / 2.f, 2385 GrStyle(stroke, nullptr)); 2386 } 2387 2388 // prefer the device space ellipse op for batchability 2389 if (viewMatrix.rectStaysRect()) { 2390 return EllipseOp::Make(std::move(paint), viewMatrix, oval, stroke); 2391 } 2392 2393 // Otherwise, if we have shader derivative support, render as device-independent 2394 if (shaderCaps->shaderDerivativeSupport()) { 2395 return DIEllipseOp::Make(std::move(paint), viewMatrix, oval, stroke); 2396 } 2397 2398 return nullptr; 2399 } 2400 2401 /////////////////////////////////////////////////////////////////////////////// 2402 2403 std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeArcOp(GrPaint&& paint, const SkMatrix& viewMatrix, 2404 const SkRect& oval, SkScalar startAngle, 2405 SkScalar sweepAngle, bool useCenter, 2406 const GrStyle& style, 2407 const GrShaderCaps* shaderCaps) { 2408 SkASSERT(!oval.isEmpty()); 2409 SkASSERT(sweepAngle); 2410 SkScalar width = oval.width(); 2411 if (SkScalarAbs(sweepAngle) >= 360.f) { 2412 return nullptr; 2413 } 2414 if (!SkScalarNearlyEqual(width, oval.height()) || !circle_stays_circle(viewMatrix)) { 2415 return nullptr; 2416 } 2417 SkPoint center = {oval.centerX(), oval.centerY()}; 2418 CircleOp::ArcParams arcParams = {SkDegreesToRadians(startAngle), SkDegreesToRadians(sweepAngle), 2419 useCenter}; 2420 return CircleOp::Make(std::move(paint), viewMatrix, center, width / 2.f, style, &arcParams); 2421 } 2422 2423 /////////////////////////////////////////////////////////////////////////////// 2424 2425 #if GR_TEST_UTILS 2426 2427 GR_DRAW_OP_TEST_DEFINE(CircleOp) { 2428 do { 2429 SkScalar rotate = random->nextSScalar1() * 360.f; 2430 SkScalar translateX = random->nextSScalar1() * 1000.f; 2431 SkScalar translateY = random->nextSScalar1() * 1000.f; 2432 SkScalar scale = random->nextSScalar1() * 100.f; 2433 SkMatrix viewMatrix; 2434 viewMatrix.setRotate(rotate); 2435 viewMatrix.postTranslate(translateX, translateY); 2436 viewMatrix.postScale(scale, scale); 2437 SkRect circle = GrTest::TestSquare(random); 2438 SkPoint center = {circle.centerX(), circle.centerY()}; 2439 SkScalar radius = circle.width() / 2.f; 2440 SkStrokeRec stroke = GrTest::TestStrokeRec(random); 2441 CircleOp::ArcParams arcParamsTmp; 2442 const CircleOp::ArcParams* arcParams = nullptr; 2443 if (random->nextBool()) { 2444 arcParamsTmp.fStartAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2; 2445 arcParamsTmp.fSweepAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2 - .01f; 2446 arcParamsTmp.fUseCenter = random->nextBool(); 2447 arcParams = &arcParamsTmp; 2448 } 2449 std::unique_ptr<GrDrawOp> op = CircleOp::Make(std::move(paint), viewMatrix, center, radius, 2450 GrStyle(stroke, nullptr), arcParams); 2451 if (op) { 2452 return op; 2453 } 2454 } while (true); 2455 } 2456 2457 GR_DRAW_OP_TEST_DEFINE(EllipseOp) { 2458 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random); 2459 SkRect ellipse = GrTest::TestSquare(random); 2460 return EllipseOp::Make(std::move(paint), viewMatrix, ellipse, GrTest::TestStrokeRec(random)); 2461 } 2462 2463 GR_DRAW_OP_TEST_DEFINE(DIEllipseOp) { 2464 SkMatrix viewMatrix = GrTest::TestMatrix(random); 2465 SkRect ellipse = GrTest::TestSquare(random); 2466 return DIEllipseOp::Make(std::move(paint), viewMatrix, ellipse, GrTest::TestStrokeRec(random)); 2467 } 2468 2469 GR_DRAW_OP_TEST_DEFINE(RRectOp) { 2470 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random); 2471 const SkRRect& rrect = GrTest::TestRRectSimple(random); 2472 return make_rrect_op(std::move(paint), viewMatrix, rrect, GrTest::TestStrokeRec(random)); 2473 } 2474 2475 #endif 2476