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 "src/gpu/ops/GrOvalOpFactory.h" 9 10#include "include/core/SkStrokeRec.h" 11#include "src/core/SkMatrixPriv.h" 12#include "src/core/SkRRectPriv.h" 13#include "src/gpu/BufferWriter.h" 14#include "src/gpu/GrCaps.h" 15#include "src/gpu/GrDrawOpTest.h" 16#include "src/gpu/GrGeometryProcessor.h" 17#include "src/gpu/GrOpFlushState.h" 18#include "src/gpu/GrProcessor.h" 19#include "src/gpu/GrProgramInfo.h" 20#include "src/gpu/GrResourceProvider.h" 21#include "src/gpu/GrShaderCaps.h" 22#include "src/gpu/GrStyle.h" 23#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" 24#include "src/gpu/glsl/GrGLSLProgramDataManager.h" 25#include "src/gpu/glsl/GrGLSLUniformHandler.h" 26#include "src/gpu/glsl/GrGLSLVarying.h" 27#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" 28#include "src/gpu/ops/GrMeshDrawOp.h" 29#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" 30 31#include <utility> 32 33using skgpu::VertexWriter; 34 35namespace { 36 37static inline bool circle_stays_circle(const SkMatrix& m) { return m.isSimilarity(); } 38 39// Produces TriStrip vertex data for an origin-centered rectangle from [-x, -y] to [x, y] 40static inline VertexWriter::TriStrip<float> origin_centered_tri_strip(float x, float y) { 41 return VertexWriter::TriStrip<float>{ -x, -y, x, y }; 42}; 43 44} // namespace 45 46/////////////////////////////////////////////////////////////////////////////// 47 48/** 49 * The output of this effect is a modulation of the input color and coverage for a circle. It 50 * operates in a space normalized by the circle radius (outer radius in the case of a stroke) 51 * with origin at the circle center. Three vertex attributes are used: 52 * vec2f : position in device space of the bounding geometry vertices 53 * vec4ub: color 54 * vec4f : (p.xy, outerRad, innerRad) 55 * p is the position in the normalized space. 56 * outerRad is the outerRadius in device space. 57 * innerRad is the innerRadius in normalized space (ignored if not stroking). 58 * Additional clip planes are supported for rendering circular arcs. The additional planes are 59 * either intersected or unioned together. Up to three planes are supported (an initial plane, 60 * a plane intersected with the initial plane, and a plane unioned with the first two). Only two 61 * are useful for any given arc, but having all three in one instance allows combining different 62 * types of arcs. 63 * Round caps for stroking are allowed as well. The caps are specified as two circle center points 64 * in the same space as p.xy. 65 */ 66 67class CircleGeometryProcessor : public GrGeometryProcessor { 68public: 69 static GrGeometryProcessor* Make(SkArenaAlloc* arena, bool stroke, bool clipPlane, 70 bool isectPlane, bool unionPlane, bool roundCaps, 71 bool wideColor, const SkMatrix& localMatrix) { 72 return arena->make([&](void* ptr) { 73 return new (ptr) CircleGeometryProcessor(stroke, clipPlane, isectPlane, unionPlane, 74 roundCaps, wideColor, localMatrix); 75 }); 76 } 77 78 const char* name() const override { return "CircleGeometryProcessor"; } 79 80 SkString getShaderDfxInfo() const override { 81 SkString format; 82 format.printf("ShaderDfx_CircleGeometry_%d_%d_%d_%d_%d_%d_%d_%d", fStroke, fInClipPlane.isInitialized(), 83 fInIsectPlane.isInitialized(), fInUnionPlane.isInitialized(), fInRoundCapCenters.isInitialized(), 84 fLocalMatrix.isIdentity(), fLocalMatrix.isScaleTranslate(), fLocalMatrix.hasPerspective()); 85 return format; 86 } 87 88 void addToKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { 89 b->addBool(fStroke, "stroked" ); 90 b->addBool(fInClipPlane.isInitialized(), "clipPlane" ); 91 b->addBool(fInIsectPlane.isInitialized(), "isectPlane" ); 92 b->addBool(fInUnionPlane.isInitialized(), "unionPlane" ); 93 b->addBool(fInRoundCapCenters.isInitialized(), "roundCapCenters"); 94 b->addBits(ProgramImpl::kMatrixKeyBits, 95 ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix), 96 "localMatrixType"); 97 } 98 99 std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override { 100 return std::make_unique<Impl>(); 101 } 102 103private: 104 CircleGeometryProcessor(bool stroke, bool clipPlane, bool isectPlane, bool unionPlane, 105 bool roundCaps, bool wideColor, const SkMatrix& localMatrix) 106 : INHERITED(kCircleGeometryProcessor_ClassID) 107 , fLocalMatrix(localMatrix) 108 , fStroke(stroke) { 109 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; 110 fInColor = MakeColorAttribute("inColor", wideColor); 111 fInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; 112 113 if (clipPlane) { 114 fInClipPlane = {"inClipPlane", kFloat3_GrVertexAttribType, kHalf3_GrSLType}; 115 } 116 if (isectPlane) { 117 fInIsectPlane = {"inIsectPlane", kFloat3_GrVertexAttribType, kHalf3_GrSLType}; 118 } 119 if (unionPlane) { 120 fInUnionPlane = {"inUnionPlane", kFloat3_GrVertexAttribType, kHalf3_GrSLType}; 121 } 122 if (roundCaps) { 123 SkASSERT(stroke); 124 SkASSERT(clipPlane); 125 fInRoundCapCenters = 126 {"inRoundCapCenters", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; 127 } 128 this->setVertexAttributes(&fInPosition, 7); 129 } 130 131 class Impl : public ProgramImpl { 132 public: 133 void setData(const GrGLSLProgramDataManager& pdman, 134 const GrShaderCaps& shaderCaps, 135 const GrGeometryProcessor& geomProc) override { 136 SetTransform(pdman, 137 shaderCaps, 138 fLocalMatrixUniform, 139 geomProc.cast<CircleGeometryProcessor>().fLocalMatrix, 140 &fLocalMatrix); 141 } 142 143 private: 144 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { 145 const CircleGeometryProcessor& cgp = args.fGeomProc.cast<CircleGeometryProcessor>(); 146 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; 147 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; 148 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; 149 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; 150 151 // emit attributes 152 varyingHandler->emitAttributes(cgp); 153 fragBuilder->codeAppend("float4 circleEdge;"); 154 varyingHandler->addPassThroughAttribute(cgp.fInCircleEdge.asShaderVar(), "circleEdge"); 155 if (cgp.fInClipPlane.isInitialized()) { 156 fragBuilder->codeAppend("half3 clipPlane;"); 157 varyingHandler->addPassThroughAttribute(cgp.fInClipPlane.asShaderVar(), 158 "clipPlane"); 159 } 160 if (cgp.fInIsectPlane.isInitialized()) { 161 fragBuilder->codeAppend("half3 isectPlane;"); 162 varyingHandler->addPassThroughAttribute(cgp.fInIsectPlane.asShaderVar(), 163 "isectPlane"); 164 } 165 if (cgp.fInUnionPlane.isInitialized()) { 166 SkASSERT(cgp.fInClipPlane.isInitialized()); 167 fragBuilder->codeAppend("half3 unionPlane;"); 168 varyingHandler->addPassThroughAttribute(cgp.fInUnionPlane.asShaderVar(), 169 "unionPlane"); 170 } 171 GrGLSLVarying capRadius(kFloat_GrSLType); 172 if (cgp.fInRoundCapCenters.isInitialized()) { 173 fragBuilder->codeAppend("float4 roundCapCenters;"); 174 varyingHandler->addPassThroughAttribute(cgp.fInRoundCapCenters.asShaderVar(), 175 "roundCapCenters"); 176 varyingHandler->addVarying("capRadius", &capRadius, 177 GrGLSLVaryingHandler::Interpolation::kCanBeFlat); 178 // This is the cap radius in normalized space where the outer radius is 1 and 179 // circledEdge.w is the normalized inner radius. 180 vertBuilder->codeAppendf("%s = (1.0 - %s.w) / 2.0;", capRadius.vsOut(), 181 cgp.fInCircleEdge.name()); 182 } 183 184 // setup pass through color 185 fragBuilder->codeAppendf("half4 %s;", args.fOutputColor); 186 varyingHandler->addPassThroughAttribute(cgp.fInColor.asShaderVar(), args.fOutputColor); 187 188 // Setup position 189 WriteOutputPosition(vertBuilder, gpArgs, cgp.fInPosition.name()); 190 WriteLocalCoord(vertBuilder, 191 uniformHandler, 192 *args.fShaderCaps, 193 gpArgs, 194 cgp.fInPosition.asShaderVar(), 195 cgp.fLocalMatrix, 196 &fLocalMatrixUniform); 197 198 fragBuilder->codeAppend("float d = length(circleEdge.xy);"); 199 fragBuilder->codeAppend("half distanceToOuterEdge = half(circleEdge.z * (1.0 - d));"); 200 fragBuilder->codeAppend("half edgeAlpha = saturate(distanceToOuterEdge);"); 201 if (cgp.fStroke) { 202 fragBuilder->codeAppend( 203 "half distanceToInnerEdge = half(circleEdge.z * (d - circleEdge.w));"); 204 fragBuilder->codeAppend("half innerAlpha = saturate(distanceToInnerEdge);"); 205 fragBuilder->codeAppend("edgeAlpha *= innerAlpha;"); 206 } 207 208 if (cgp.fInClipPlane.isInitialized()) { 209 fragBuilder->codeAppend( 210 "half clip = half(saturate(circleEdge.z * dot(circleEdge.xy, " 211 "clipPlane.xy) + clipPlane.z));"); 212 if (cgp.fInIsectPlane.isInitialized()) { 213 fragBuilder->codeAppend( 214 "clip *= half(saturate(circleEdge.z * dot(circleEdge.xy, " 215 "isectPlane.xy) + isectPlane.z));"); 216 } 217 if (cgp.fInUnionPlane.isInitialized()) { 218 fragBuilder->codeAppend( 219 "clip = saturate(clip + half(saturate(circleEdge.z * dot(circleEdge.xy," 220 " unionPlane.xy) + unionPlane.z)));"); 221 } 222 fragBuilder->codeAppend("edgeAlpha *= clip;"); 223 if (cgp.fInRoundCapCenters.isInitialized()) { 224 // We compute coverage of the round caps as circles at the butt caps produced 225 // by the clip planes. The inverse of the clip planes is applied so that there 226 // is no double counting. 227 fragBuilder->codeAppendf( 228 "half dcap1 = half(circleEdge.z * (%s - length(circleEdge.xy - " 229 " roundCapCenters.xy)));" 230 "half dcap2 = half(circleEdge.z * (%s - length(circleEdge.xy - " 231 " roundCapCenters.zw)));" 232 "half capAlpha = (1 - clip) * (max(dcap1, 0) + max(dcap2, 0));" 233 "edgeAlpha = min(edgeAlpha + capAlpha, 1.0);", 234 capRadius.fsIn(), capRadius.fsIn()); 235 } 236 } 237 fragBuilder->codeAppendf("half4 %s = half4(edgeAlpha);", args.fOutputCoverage); 238 } 239 240 SkMatrix fLocalMatrix = SkMatrix::InvalidMatrix(); 241 UniformHandle fLocalMatrixUniform; 242 }; 243 244 SkMatrix fLocalMatrix; 245 246 Attribute fInPosition; 247 Attribute fInColor; 248 Attribute fInCircleEdge; 249 // Optional attributes. 250 Attribute fInClipPlane; 251 Attribute fInIsectPlane; 252 Attribute fInUnionPlane; 253 Attribute fInRoundCapCenters; 254 255 bool fStroke; 256 GR_DECLARE_GEOMETRY_PROCESSOR_TEST 257 258 using INHERITED = GrGeometryProcessor; 259}; 260 261GR_DEFINE_GEOMETRY_PROCESSOR_TEST(CircleGeometryProcessor); 262 263#if GR_TEST_UTILS 264GrGeometryProcessor* CircleGeometryProcessor::TestCreate(GrProcessorTestData* d) { 265 bool stroke = d->fRandom->nextBool(); 266 bool roundCaps = stroke ? d->fRandom->nextBool() : false; 267 bool wideColor = d->fRandom->nextBool(); 268 bool clipPlane = d->fRandom->nextBool(); 269 bool isectPlane = d->fRandom->nextBool(); 270 bool unionPlane = d->fRandom->nextBool(); 271 const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom); 272 return CircleGeometryProcessor::Make(d->allocator(), stroke, clipPlane, isectPlane, 273 unionPlane, roundCaps, wideColor, matrix); 274} 275#endif 276 277class ButtCapDashedCircleGeometryProcessor : public GrGeometryProcessor { 278public: 279 static GrGeometryProcessor* Make(SkArenaAlloc* arena, bool wideColor, 280 const SkMatrix& localMatrix) { 281 return arena->make([&](void* ptr) { 282 return new (ptr) ButtCapDashedCircleGeometryProcessor(wideColor, localMatrix); 283 }); 284 } 285 286 ~ButtCapDashedCircleGeometryProcessor() override {} 287 288 const char* name() const override { return "ButtCapDashedCircleGeometryProcessor"; } 289 290 SkString getShaderDfxInfo() const override { 291 SkString format; 292 format.printf("ShaderDfx_ButtCapDashedCircle_%d_%d_%d", 293 fLocalMatrix.isIdentity(), fLocalMatrix.isScaleTranslate(), fLocalMatrix.hasPerspective()); 294 return format; 295 } 296 297 void addToKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { 298 b->addBits(ProgramImpl::kMatrixKeyBits, 299 ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix), 300 "localMatrixType"); 301 } 302 303 std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override { 304 return std::make_unique<Impl>(); 305 } 306 307private: 308 ButtCapDashedCircleGeometryProcessor(bool wideColor, const SkMatrix& localMatrix) 309 : INHERITED(kButtCapStrokedCircleGeometryProcessor_ClassID) 310 , fLocalMatrix(localMatrix) { 311 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; 312 fInColor = MakeColorAttribute("inColor", wideColor); 313 fInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; 314 fInDashParams = {"inDashParams", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; 315 this->setVertexAttributes(&fInPosition, 4); 316 } 317 318 class Impl : public ProgramImpl { 319 public: 320 void setData(const GrGLSLProgramDataManager& pdman, 321 const GrShaderCaps& shaderCaps, 322 const GrGeometryProcessor& geomProc) override { 323 SetTransform(pdman, 324 shaderCaps, 325 fLocalMatrixUniform, 326 geomProc.cast<ButtCapDashedCircleGeometryProcessor>().fLocalMatrix, 327 &fLocalMatrix); 328 } 329 330 private: 331 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { 332 const ButtCapDashedCircleGeometryProcessor& bcscgp = 333 args.fGeomProc.cast<ButtCapDashedCircleGeometryProcessor>(); 334 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; 335 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; 336 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; 337 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; 338 339 // emit attributes 340 varyingHandler->emitAttributes(bcscgp); 341 fragBuilder->codeAppend("float4 circleEdge;"); 342 varyingHandler->addPassThroughAttribute(bcscgp.fInCircleEdge.asShaderVar(), 343 "circleEdge"); 344 345 fragBuilder->codeAppend("float4 dashParams;"); 346 varyingHandler->addPassThroughAttribute( 347 bcscgp.fInDashParams.asShaderVar(), 348 "dashParams", 349 GrGLSLVaryingHandler::Interpolation::kCanBeFlat); 350 GrGLSLVarying wrapDashes(kHalf4_GrSLType); 351 varyingHandler->addVarying("wrapDashes", &wrapDashes, 352 GrGLSLVaryingHandler::Interpolation::kCanBeFlat); 353 GrGLSLVarying lastIntervalLength(kHalf_GrSLType); 354 varyingHandler->addVarying("lastIntervalLength", &lastIntervalLength, 355 GrGLSLVaryingHandler::Interpolation::kCanBeFlat); 356 vertBuilder->codeAppendf("float4 dashParams = %s;", bcscgp.fInDashParams.name()); 357 // Our fragment shader works in on/off intervals as specified by dashParams.xy: 358 // x = length of on interval, y = length of on + off. 359 // There are two other parameters in dashParams.zw: 360 // z = start angle in radians, w = phase offset in radians in range -y/2..y/2. 361 // Each interval has a "corresponding" dash which may be shifted partially or 362 // fully out of its interval by the phase. So there may be up to two "visual" 363 // dashes in an interval. 364 // When computing coverage in an interval we look at three dashes. These are the 365 // "corresponding" dashes from the current, previous, and next intervals. Any of these 366 // may be phase shifted into our interval or even when phase=0 they may be within half a 367 // pixel distance of a pixel center in the interval. 368 // When in the first interval we need to check the dash from the last interval. And 369 // similarly when in the last interval we need to check the dash from the first 370 // interval. When 2pi is not perfectly divisible dashParams.y this is a boundary case. 371 // We compute the dash begin/end angles in the vertex shader and apply them in the 372 // fragment shader when we detect we're in the first/last interval. 373 vertBuilder->codeAppend(R"( 374 // The two boundary dash intervals are stored in wrapDashes.xy and .zw and fed 375 // to the fragment shader as a varying. 376 float4 wrapDashes; 377 half lastIntervalLength = mod(6.28318530718, half(dashParams.y)); 378 // We can happen to be perfectly divisible. 379 if (0 == lastIntervalLength) { 380 lastIntervalLength = half(dashParams.y); 381 } 382 // Let 'l' be the last interval before reaching 2 pi. 383 // Based on the phase determine whether (l-1)th, l-th, or (l+1)th interval's 384 // "corresponding" dash appears in the l-th interval and is closest to the 0-th 385 // interval. 386 half offset = 0; 387 if (-dashParams.w >= lastIntervalLength) { 388 offset = half(-dashParams.y); 389 } else if (dashParams.w > dashParams.y - lastIntervalLength) { 390 offset = half(dashParams.y); 391 } 392 wrapDashes.x = -lastIntervalLength + offset - dashParams.w; 393 // The end of this dash may be beyond the 2 pi and therefore clipped. Hence the 394 // min. 395 wrapDashes.y = min(wrapDashes.x + dashParams.x, 0); 396 397 // Based on the phase determine whether the -1st, 0th, or 1st interval's 398 // "corresponding" dash appears in the 0th interval and is closest to l. 399 offset = 0; 400 if (dashParams.w >= dashParams.x) { 401 offset = half(dashParams.y); 402 } else if (-dashParams.w > dashParams.y - dashParams.x) { 403 offset = half(-dashParams.y); 404 } 405 wrapDashes.z = lastIntervalLength + offset - dashParams.w; 406 wrapDashes.w = wrapDashes.z + dashParams.x; 407 // The start of the dash we're considering may be clipped by the start of the 408 // circle. 409 wrapDashes.z = max(wrapDashes.z, lastIntervalLength); 410 )"); 411 vertBuilder->codeAppendf("%s = half4(wrapDashes);", wrapDashes.vsOut()); 412 vertBuilder->codeAppendf("%s = lastIntervalLength;", lastIntervalLength.vsOut()); 413 fragBuilder->codeAppendf("half4 wrapDashes = %s;", wrapDashes.fsIn()); 414 fragBuilder->codeAppendf("half lastIntervalLength = %s;", lastIntervalLength.fsIn()); 415 416 // setup pass through color 417 fragBuilder->codeAppendf("half4 %s;", args.fOutputColor); 418 varyingHandler->addPassThroughAttribute( 419 bcscgp.fInColor.asShaderVar(), 420 args.fOutputColor, 421 GrGLSLVaryingHandler::Interpolation::kCanBeFlat); 422 423 // Setup position 424 WriteOutputPosition(vertBuilder, gpArgs, bcscgp.fInPosition.name()); 425 WriteLocalCoord(vertBuilder, 426 uniformHandler, 427 *args.fShaderCaps, 428 gpArgs, 429 bcscgp.fInPosition.asShaderVar(), 430 bcscgp.fLocalMatrix, 431 &fLocalMatrixUniform); 432 433 GrShaderVar fnArgs[] = { 434 GrShaderVar("angleToEdge", kFloat_GrSLType), 435 GrShaderVar("diameter", kFloat_GrSLType), 436 }; 437 SkString fnName = fragBuilder->getMangledFunctionName("coverage_from_dash_edge"); 438 fragBuilder->emitFunction(kFloat_GrSLType, fnName.c_str(), 439 {fnArgs, SK_ARRAY_COUNT(fnArgs)}, R"( 440 float linearDist; 441 angleToEdge = clamp(angleToEdge, -3.1415, 3.1415); 442 linearDist = diameter * sin(angleToEdge / 2); 443 return saturate(linearDist + 0.5); 444 )"); 445 fragBuilder->codeAppend(R"( 446 float d = length(circleEdge.xy) * circleEdge.z; 447 448 // Compute coverage from outer/inner edges of the stroke. 449 half distanceToOuterEdge = half(circleEdge.z - d); 450 half edgeAlpha = saturate(distanceToOuterEdge); 451 half distanceToInnerEdge = half(d - circleEdge.z * circleEdge.w); 452 half innerAlpha = saturate(distanceToInnerEdge); 453 edgeAlpha *= innerAlpha; 454 455 half angleFromStart = half(atan(circleEdge.y, circleEdge.x) - dashParams.z); 456 angleFromStart = mod(angleFromStart, 6.28318530718); 457 float x = mod(angleFromStart, dashParams.y); 458 // Convert the radial distance from center to pixel into a diameter. 459 d *= 2; 460 half2 currDash = half2(half(-dashParams.w), half(dashParams.x) - 461 half(dashParams.w)); 462 half2 nextDash = half2(half(dashParams.y) - half(dashParams.w), 463 half(dashParams.y) + half(dashParams.x) - 464 half(dashParams.w)); 465 half2 prevDash = half2(half(-dashParams.y) - half(dashParams.w), 466 half(-dashParams.y) + half(dashParams.x) - 467 half(dashParams.w)); 468 half dashAlpha = 0; 469 )"); 470 fragBuilder->codeAppendf(R"( 471 if (angleFromStart - x + dashParams.y >= 6.28318530718) { 472 dashAlpha += half(%s(x - wrapDashes.z, d) * %s(wrapDashes.w - x, d)); 473 currDash.y = min(currDash.y, lastIntervalLength); 474 if (nextDash.x >= lastIntervalLength) { 475 // The next dash is outside the 0..2pi range, throw it away 476 nextDash.xy = half2(1000); 477 } else { 478 // Clip the end of the next dash to the end of the circle 479 nextDash.y = min(nextDash.y, lastIntervalLength); 480 } 481 } 482 )", fnName.c_str(), fnName.c_str()); 483 fragBuilder->codeAppendf(R"( 484 if (angleFromStart - x - dashParams.y < -0.01) { 485 dashAlpha += half(%s(x - wrapDashes.x, d) * %s(wrapDashes.y - x, d)); 486 currDash.x = max(currDash.x, 0); 487 if (prevDash.y <= 0) { 488 // The previous dash is outside the 0..2pi range, throw it away 489 prevDash.xy = half2(1000); 490 } else { 491 // Clip the start previous dash to the start of the circle 492 prevDash.x = max(prevDash.x, 0); 493 } 494 } 495 )", fnName.c_str(), fnName.c_str()); 496 fragBuilder->codeAppendf(R"( 497 dashAlpha += half(%s(x - currDash.x, d) * %s(currDash.y - x, d)); 498 dashAlpha += half(%s(x - nextDash.x, d) * %s(nextDash.y - x, d)); 499 dashAlpha += half(%s(x - prevDash.x, d) * %s(prevDash.y - x, d)); 500 dashAlpha = min(dashAlpha, 1); 501 edgeAlpha *= dashAlpha; 502 )", fnName.c_str(), fnName.c_str(), fnName.c_str(), fnName.c_str(), fnName.c_str(), 503 fnName.c_str()); 504 fragBuilder->codeAppendf("half4 %s = half4(edgeAlpha);", args.fOutputCoverage); 505 } 506 507 SkMatrix fLocalMatrix = SkMatrix::InvalidMatrix(); 508 UniformHandle fLocalMatrixUniform; 509 }; 510 511 SkMatrix fLocalMatrix; 512 Attribute fInPosition; 513 Attribute fInColor; 514 Attribute fInCircleEdge; 515 Attribute fInDashParams; 516 517 GR_DECLARE_GEOMETRY_PROCESSOR_TEST 518 519 using INHERITED = GrGeometryProcessor; 520}; 521 522#if GR_TEST_UTILS 523GrGeometryProcessor* ButtCapDashedCircleGeometryProcessor::TestCreate(GrProcessorTestData* d) { 524 bool wideColor = d->fRandom->nextBool(); 525 const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom); 526 return ButtCapDashedCircleGeometryProcessor::Make(d->allocator(), wideColor, matrix); 527} 528#endif 529 530/////////////////////////////////////////////////////////////////////////////// 531 532/** 533 * The output of this effect is a modulation of the input color and coverage for an axis-aligned 534 * ellipse, specified as a 2D offset from center, and the reciprocals of the outer and inner radii, 535 * in both x and y directions. 536 * 537 * We are using an implicit function of x^2/a^2 + y^2/b^2 - 1 = 0. 538 */ 539 540class EllipseGeometryProcessor : public GrGeometryProcessor { 541public: 542 static GrGeometryProcessor* Make(SkArenaAlloc* arena, bool stroke, bool wideColor, 543 bool useScale, const SkMatrix& localMatrix) { 544 return arena->make([&](void* ptr) { 545 return new (ptr) EllipseGeometryProcessor(stroke, wideColor, useScale, localMatrix); 546 }); 547 } 548 549 ~EllipseGeometryProcessor() override {} 550 551 const char* name() const override { return "EllipseGeometryProcessor"; } 552 553 SkString getShaderDfxInfo() const override { 554 SkString format; 555 format.printf("ShaderDfx_EllipseGeometry_%d_%d_%d_%d", fStroke, 556 fLocalMatrix.isIdentity(), fLocalMatrix.isScaleTranslate(), fLocalMatrix.hasPerspective()); 557 return format; 558 } 559 560 void addToKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { 561 b->addBool(fStroke, "stroked"); 562 b->addBits(ProgramImpl::kMatrixKeyBits, 563 ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix), 564 "localMatrixType"); 565 } 566 567 std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override { 568 return std::make_unique<Impl>(); 569 } 570 571private: 572 EllipseGeometryProcessor(bool stroke, bool wideColor, bool useScale, 573 const SkMatrix& localMatrix) 574 : INHERITED(kEllipseGeometryProcessor_ClassID) 575 , fLocalMatrix(localMatrix) 576 , fStroke(stroke) 577 , fUseScale(useScale) { 578 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; 579 fInColor = MakeColorAttribute("inColor", wideColor); 580 if (useScale) { 581 fInEllipseOffset = {"inEllipseOffset", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; 582 } else { 583 fInEllipseOffset = {"inEllipseOffset", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; 584 } 585 fInEllipseRadii = {"inEllipseRadii", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; 586 this->setVertexAttributes(&fInPosition, 4); 587 } 588 589 class Impl : public ProgramImpl { 590 public: 591 void setData(const GrGLSLProgramDataManager& pdman, 592 const GrShaderCaps& shaderCaps, 593 const GrGeometryProcessor& geomProc) override { 594 const EllipseGeometryProcessor& egp = geomProc.cast<EllipseGeometryProcessor>(); 595 SetTransform(pdman, shaderCaps, fLocalMatrixUniform, egp.fLocalMatrix, &fLocalMatrix); 596 } 597 598 private: 599 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { 600 const EllipseGeometryProcessor& egp = args.fGeomProc.cast<EllipseGeometryProcessor>(); 601 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; 602 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; 603 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; 604 605 // emit attributes 606 varyingHandler->emitAttributes(egp); 607 608 GrSLType offsetType = egp.fUseScale ? kFloat3_GrSLType : kFloat2_GrSLType; 609 GrGLSLVarying ellipseOffsets(offsetType); 610 varyingHandler->addVarying("EllipseOffsets", &ellipseOffsets); 611 vertBuilder->codeAppendf("%s = %s;", ellipseOffsets.vsOut(), 612 egp.fInEllipseOffset.name()); 613 614 GrGLSLVarying ellipseRadii(kFloat4_GrSLType); 615 varyingHandler->addVarying("EllipseRadii", &ellipseRadii); 616 vertBuilder->codeAppendf("%s = %s;", ellipseRadii.vsOut(), egp.fInEllipseRadii.name()); 617 618 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; 619 // setup pass through color 620 fragBuilder->codeAppendf("half4 %s;", args.fOutputColor); 621 varyingHandler->addPassThroughAttribute(egp.fInColor.asShaderVar(), args.fOutputColor); 622 623 // Setup position 624 WriteOutputPosition(vertBuilder, gpArgs, egp.fInPosition.name()); 625 WriteLocalCoord(vertBuilder, 626 uniformHandler, 627 *args.fShaderCaps, 628 gpArgs, 629 egp.fInPosition.asShaderVar(), 630 egp.fLocalMatrix, 631 &fLocalMatrixUniform); 632 633 // For stroked ellipses, we use the full ellipse equation (x^2/a^2 + y^2/b^2 = 1) 634 // to compute both the edges because we need two separate test equations for 635 // the single offset. 636 // For filled ellipses we can use a unit circle equation (x^2 + y^2 = 1), and warp 637 // the distance by the gradient, non-uniformly scaled by the inverse of the 638 // ellipse size. 639 640 // On medium precision devices, we scale the denominator of the distance equation 641 // before taking the inverse square root to minimize the chance that we're dividing 642 // by zero, then we scale the result back. 643 644 // for outer curve 645 fragBuilder->codeAppendf("float2 offset = %s.xy;", ellipseOffsets.fsIn()); 646 if (egp.fStroke) { 647 fragBuilder->codeAppendf("offset *= %s.xy;", ellipseRadii.fsIn()); 648 } 649 fragBuilder->codeAppend("float test = dot(offset, offset) - 1.0;"); 650 if (egp.fUseScale) { 651 fragBuilder->codeAppendf("float2 grad = 2.0*offset*(%s.z*%s.xy);", 652 ellipseOffsets.fsIn(), ellipseRadii.fsIn()); 653 } else { 654 fragBuilder->codeAppendf("float2 grad = 2.0*offset*%s.xy;", ellipseRadii.fsIn()); 655 } 656 fragBuilder->codeAppend("float grad_dot = dot(grad, grad);"); 657 658 // avoid calling inversesqrt on zero. 659 if (args.fShaderCaps->floatIs32Bits()) { 660 fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.1755e-38);"); 661 } else { 662 fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);"); 663 } 664 if (egp.fUseScale) { 665 fragBuilder->codeAppendf("float invlen = %s.z*inversesqrt(grad_dot);", 666 ellipseOffsets.fsIn()); 667 } else { 668 fragBuilder->codeAppend("float invlen = inversesqrt(grad_dot);"); 669 } 670 fragBuilder->codeAppend("float edgeAlpha = saturate(0.5-test*invlen);"); 671 672 // for inner curve 673 if (egp.fStroke) { 674 fragBuilder->codeAppendf("offset = %s.xy*%s.zw;", ellipseOffsets.fsIn(), 675 ellipseRadii.fsIn()); 676 fragBuilder->codeAppend("test = dot(offset, offset) - 1.0;"); 677 if (egp.fUseScale) { 678 fragBuilder->codeAppendf("grad = 2.0*offset*(%s.z*%s.zw);", 679 ellipseOffsets.fsIn(), ellipseRadii.fsIn()); 680 } else { 681 fragBuilder->codeAppendf("grad = 2.0*offset*%s.zw;", ellipseRadii.fsIn()); 682 } 683 fragBuilder->codeAppend("grad_dot = dot(grad, grad);"); 684 if (!args.fShaderCaps->floatIs32Bits()) { 685 fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);"); 686 } 687 if (egp.fUseScale) { 688 fragBuilder->codeAppendf("invlen = %s.z*inversesqrt(grad_dot);", 689 ellipseOffsets.fsIn()); 690 } else { 691 fragBuilder->codeAppend("invlen = inversesqrt(grad_dot);"); 692 } 693 fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);"); 694 } 695 696 fragBuilder->codeAppendf("half4 %s = half4(half(edgeAlpha));", args.fOutputCoverage); 697 } 698 699 using INHERITED = ProgramImpl; 700 701 SkMatrix fLocalMatrix = SkMatrix::InvalidMatrix(); 702 UniformHandle fLocalMatrixUniform; 703 }; 704 705 Attribute fInPosition; 706 Attribute fInColor; 707 Attribute fInEllipseOffset; 708 Attribute fInEllipseRadii; 709 710 SkMatrix fLocalMatrix; 711 bool fStroke; 712 bool fUseScale; 713 714 GR_DECLARE_GEOMETRY_PROCESSOR_TEST 715 716 using INHERITED = GrGeometryProcessor; 717}; 718 719GR_DEFINE_GEOMETRY_PROCESSOR_TEST(EllipseGeometryProcessor); 720 721#if GR_TEST_UTILS 722GrGeometryProcessor* EllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) { 723 bool stroke = d->fRandom->nextBool(); 724 bool wideColor = d->fRandom->nextBool(); 725 bool useScale = d->fRandom->nextBool(); 726 SkMatrix matrix = GrTest::TestMatrix(d->fRandom); 727 return EllipseGeometryProcessor::Make(d->allocator(), stroke, wideColor, useScale, matrix); 728} 729#endif 730 731/////////////////////////////////////////////////////////////////////////////// 732 733/** 734 * The output of this effect is a modulation of the input color and coverage for an ellipse, 735 * specified as a 2D offset from center for both the outer and inner paths (if stroked). The 736 * implict equation used is for a unit circle (x^2 + y^2 - 1 = 0) and the edge corrected by 737 * using differentials. 738 * 739 * The result is device-independent and can be used with any affine matrix. 740 */ 741 742enum class DIEllipseStyle { kStroke = 0, kHairline, kFill }; 743 744class DIEllipseGeometryProcessor : public GrGeometryProcessor { 745public: 746 static GrGeometryProcessor* Make(SkArenaAlloc* arena, bool wideColor, bool useScale, 747 const SkMatrix& viewMatrix, DIEllipseStyle style) { 748 return arena->make([&](void* ptr) { 749 return new (ptr) DIEllipseGeometryProcessor(wideColor, useScale, viewMatrix, style); 750 }); 751 } 752 753 ~DIEllipseGeometryProcessor() override {} 754 755 const char* name() const override { return "DIEllipseGeometryProcessor"; } 756 757 SkString getShaderDfxInfo() const override { 758 SkString format; 759 format.printf("ShaderDfx_DIEllipseGeometry_%d_%d_%d_%d", fStyle, 760 fViewMatrix.isIdentity(), fViewMatrix.isScaleTranslate(), fViewMatrix.hasPerspective()); 761 return format; 762 } 763 764 void addToKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { 765 b->addBits(2, static_cast<uint32_t>(fStyle), "style"); 766 b->addBits(ProgramImpl::kMatrixKeyBits, 767 ProgramImpl::ComputeMatrixKey(caps, fViewMatrix), 768 "viewMatrixType"); 769 } 770 771 std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override { 772 return std::make_unique<Impl>(); 773 } 774 775private: 776 DIEllipseGeometryProcessor(bool wideColor, bool useScale, const SkMatrix& viewMatrix, 777 DIEllipseStyle style) 778 : INHERITED(kDIEllipseGeometryProcessor_ClassID) 779 , fViewMatrix(viewMatrix) 780 , fUseScale(useScale) 781 , fStyle(style) { 782 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; 783 fInColor = MakeColorAttribute("inColor", wideColor); 784 if (useScale) { 785 fInEllipseOffsets0 = {"inEllipseOffsets0", kFloat3_GrVertexAttribType, 786 kFloat3_GrSLType}; 787 } else { 788 fInEllipseOffsets0 = {"inEllipseOffsets0", kFloat2_GrVertexAttribType, 789 kFloat2_GrSLType}; 790 } 791 fInEllipseOffsets1 = {"inEllipseOffsets1", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; 792 this->setVertexAttributes(&fInPosition, 4); 793 } 794 795 class Impl : public ProgramImpl { 796 public: 797 void setData(const GrGLSLProgramDataManager& pdman, 798 const GrShaderCaps& shaderCaps, 799 const GrGeometryProcessor& geomProc) override { 800 const auto& diegp = geomProc.cast<DIEllipseGeometryProcessor>(); 801 802 SetTransform(pdman, shaderCaps, fViewMatrixUniform, diegp.fViewMatrix, &fViewMatrix); 803 } 804 805 private: 806 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { 807 const auto& diegp = args.fGeomProc.cast<DIEllipseGeometryProcessor>(); 808 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; 809 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; 810 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; 811 812 // emit attributes 813 varyingHandler->emitAttributes(diegp); 814 815 GrSLType offsetType = (diegp.fUseScale) ? kFloat3_GrSLType : kFloat2_GrSLType; 816 GrGLSLVarying offsets0(offsetType); 817 varyingHandler->addVarying("EllipseOffsets0", &offsets0); 818 vertBuilder->codeAppendf("%s = %s;", offsets0.vsOut(), diegp.fInEllipseOffsets0.name()); 819 820 GrGLSLVarying offsets1(kFloat2_GrSLType); 821 varyingHandler->addVarying("EllipseOffsets1", &offsets1); 822 vertBuilder->codeAppendf("%s = %s;", offsets1.vsOut(), diegp.fInEllipseOffsets1.name()); 823 824 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; 825 fragBuilder->codeAppendf("half4 %s;", args.fOutputColor); 826 varyingHandler->addPassThroughAttribute(diegp.fInColor.asShaderVar(), 827 args.fOutputColor); 828 829 // Setup position 830 WriteOutputPosition(vertBuilder, 831 uniformHandler, 832 *args.fShaderCaps, 833 gpArgs, 834 diegp.fInPosition.name(), 835 diegp.fViewMatrix, 836 &fViewMatrixUniform); 837 gpArgs->fLocalCoordVar = diegp.fInPosition.asShaderVar(); 838 839 // for outer curve 840 fragBuilder->codeAppendf("float2 scaledOffset = %s.xy;", offsets0.fsIn()); 841 fragBuilder->codeAppend("float test = dot(scaledOffset, scaledOffset) - 1.0;"); 842 fragBuilder->codeAppendf("float2 duvdx = dFdx(%s.xy);", offsets0.fsIn()); 843 fragBuilder->codeAppendf("float2 duvdy = dFdy(%s.xy);", offsets0.fsIn()); 844 fragBuilder->codeAppendf( 845 "float2 grad = float2(%s.x*duvdx.x + %s.y*duvdx.y," 846 " %s.x*duvdy.x + %s.y*duvdy.y);", 847 offsets0.fsIn(), offsets0.fsIn(), offsets0.fsIn(), offsets0.fsIn()); 848 if (diegp.fUseScale) { 849 fragBuilder->codeAppendf("grad *= %s.z;", offsets0.fsIn()); 850 } 851 852 fragBuilder->codeAppend("float grad_dot = 4.0*dot(grad, grad);"); 853 // avoid calling inversesqrt on zero. 854 if (args.fShaderCaps->floatIs32Bits()) { 855 fragBuilder->codeAppend("grad_dot = max(grad_dot, 1.1755e-38);"); 856 } else { 857 fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);"); 858 } 859 fragBuilder->codeAppend("float invlen = inversesqrt(grad_dot);"); 860 if (diegp.fUseScale) { 861 fragBuilder->codeAppendf("invlen *= %s.z;", offsets0.fsIn()); 862 } 863 if (DIEllipseStyle::kHairline == diegp.fStyle) { 864 // can probably do this with one step 865 fragBuilder->codeAppend("float edgeAlpha = saturate(1.0-test*invlen);"); 866 fragBuilder->codeAppend("edgeAlpha *= saturate(1.0+test*invlen);"); 867 } else { 868 fragBuilder->codeAppend("float edgeAlpha = saturate(0.5-test*invlen);"); 869 } 870 871 // for inner curve 872 if (DIEllipseStyle::kStroke == diegp.fStyle) { 873 fragBuilder->codeAppendf("scaledOffset = %s.xy;", offsets1.fsIn()); 874 fragBuilder->codeAppend("test = dot(scaledOffset, scaledOffset) - 1.0;"); 875 fragBuilder->codeAppendf("duvdx = float2(dFdx(%s));", offsets1.fsIn()); 876 fragBuilder->codeAppendf("duvdy = float2(dFdy(%s));", offsets1.fsIn()); 877 fragBuilder->codeAppendf( 878 "grad = float2(%s.x*duvdx.x + %s.y*duvdx.y," 879 " %s.x*duvdy.x + %s.y*duvdy.y);", 880 offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn(), offsets1.fsIn()); 881 if (diegp.fUseScale) { 882 fragBuilder->codeAppendf("grad *= %s.z;", offsets0.fsIn()); 883 } 884 fragBuilder->codeAppend("grad_dot = 4.0*dot(grad, grad);"); 885 if (!args.fShaderCaps->floatIs32Bits()) { 886 fragBuilder->codeAppend("grad_dot = max(grad_dot, 6.1036e-5);"); 887 } 888 fragBuilder->codeAppend("invlen = inversesqrt(grad_dot);"); 889 if (diegp.fUseScale) { 890 fragBuilder->codeAppendf("invlen *= %s.z;", offsets0.fsIn()); 891 } 892 fragBuilder->codeAppend("edgeAlpha *= saturate(0.5+test*invlen);"); 893 } 894 895 fragBuilder->codeAppendf("half4 %s = half4(half(edgeAlpha));", args.fOutputCoverage); 896 } 897 898 SkMatrix fViewMatrix = SkMatrix::InvalidMatrix(); 899 UniformHandle fViewMatrixUniform; 900 }; 901 902 Attribute fInPosition; 903 Attribute fInColor; 904 Attribute fInEllipseOffsets0; 905 Attribute fInEllipseOffsets1; 906 907 SkMatrix fViewMatrix; 908 bool fUseScale; 909 DIEllipseStyle fStyle; 910 911 GR_DECLARE_GEOMETRY_PROCESSOR_TEST 912 913 using INHERITED = GrGeometryProcessor; 914}; 915 916GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DIEllipseGeometryProcessor); 917 918#if GR_TEST_UTILS 919GrGeometryProcessor* DIEllipseGeometryProcessor::TestCreate(GrProcessorTestData* d) { 920 bool wideColor = d->fRandom->nextBool(); 921 bool useScale = d->fRandom->nextBool(); 922 SkMatrix matrix = GrTest::TestMatrix(d->fRandom); 923 auto style = (DIEllipseStyle)(d->fRandom->nextRangeU(0, 2)); 924 return DIEllipseGeometryProcessor::Make(d->allocator(), wideColor, useScale, matrix, style); 925} 926#endif 927 928/////////////////////////////////////////////////////////////////////////////// 929 930// We have two possible cases for geometry for a circle: 931 932// In the case of a normal fill, we draw geometry for the circle as an octagon. 933static const uint16_t gFillCircleIndices[] = { 934 // enter the octagon 935 // clang-format off 936 0, 1, 8, 1, 2, 8, 937 2, 3, 8, 3, 4, 8, 938 4, 5, 8, 5, 6, 8, 939 6, 7, 8, 7, 0, 8 940 // clang-format on 941}; 942 943// For stroked circles, we use two nested octagons. 944static const uint16_t gStrokeCircleIndices[] = { 945 // enter the octagon 946 // clang-format off 947 0, 1, 9, 0, 9, 8, 948 1, 2, 10, 1, 10, 9, 949 2, 3, 11, 2, 11, 10, 950 3, 4, 12, 3, 12, 11, 951 4, 5, 13, 4, 13, 12, 952 5, 6, 14, 5, 14, 13, 953 6, 7, 15, 6, 15, 14, 954 7, 0, 8, 7, 8, 15, 955 // clang-format on 956}; 957 958// Normalized geometry for octagons that circumscribe and lie on a circle: 959 960static constexpr SkScalar kOctOffset = 0.41421356237f; // sqrt(2) - 1 961static constexpr SkPoint kOctagonOuter[] = { 962 SkPoint::Make(-kOctOffset, -1), 963 SkPoint::Make( kOctOffset, -1), 964 SkPoint::Make( 1, -kOctOffset), 965 SkPoint::Make( 1, kOctOffset), 966 SkPoint::Make( kOctOffset, 1), 967 SkPoint::Make(-kOctOffset, 1), 968 SkPoint::Make(-1, kOctOffset), 969 SkPoint::Make(-1, -kOctOffset), 970}; 971 972// cosine and sine of pi/8 973static constexpr SkScalar kCosPi8 = 0.923579533f; 974static constexpr SkScalar kSinPi8 = 0.382683432f; 975static constexpr SkPoint kOctagonInner[] = { 976 SkPoint::Make(-kSinPi8, -kCosPi8), 977 SkPoint::Make( kSinPi8, -kCosPi8), 978 SkPoint::Make( kCosPi8, -kSinPi8), 979 SkPoint::Make( kCosPi8, kSinPi8), 980 SkPoint::Make( kSinPi8, kCosPi8), 981 SkPoint::Make(-kSinPi8, kCosPi8), 982 SkPoint::Make(-kCosPi8, kSinPi8), 983 SkPoint::Make(-kCosPi8, -kSinPi8), 984}; 985 986static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices); 987static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices); 988static const int kVertsPerStrokeCircle = 16; 989static const int kVertsPerFillCircle = 9; 990 991static int circle_type_to_vert_count(bool stroked) { 992 return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle; 993} 994 995static int circle_type_to_index_count(bool stroked) { 996 return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle; 997} 998 999static const uint16_t* circle_type_to_indices(bool stroked) { 1000 return stroked ? gStrokeCircleIndices : gFillCircleIndices; 1001} 1002 1003/////////////////////////////////////////////////////////////////////////////// 1004 1005class CircleOp final : public GrMeshDrawOp { 1006private: 1007 using Helper = GrSimpleMeshDrawOpHelper; 1008 1009public: 1010 DEFINE_OP_CLASS_ID 1011 1012 /** Optional extra params to render a partial arc rather than a full circle. */ 1013 struct ArcParams { 1014 SkScalar fStartAngleRadians; 1015 SkScalar fSweepAngleRadians; 1016 bool fUseCenter; 1017 }; 1018 1019 static GrOp::Owner Make(GrRecordingContext* context, 1020 GrPaint&& paint, 1021 const SkMatrix& viewMatrix, 1022 SkPoint center, 1023 SkScalar radius, 1024 const GrStyle& style, 1025 const ArcParams* arcParams = nullptr) { 1026 SkASSERT(circle_stays_circle(viewMatrix)); 1027 if (style.hasPathEffect()) { 1028 return nullptr; 1029 } 1030 const SkStrokeRec& stroke = style.strokeRec(); 1031 SkStrokeRec::Style recStyle = stroke.getStyle(); 1032 if (arcParams) { 1033 // Arc support depends on the style. 1034 switch (recStyle) { 1035 case SkStrokeRec::kStrokeAndFill_Style: 1036 // This produces a strange result that this op doesn't implement. 1037 return nullptr; 1038 case SkStrokeRec::kFill_Style: 1039 // This supports all fills. 1040 break; 1041 case SkStrokeRec::kStroke_Style: 1042 // Strokes that don't use the center point are supported with butt and round 1043 // caps. 1044 if (arcParams->fUseCenter || stroke.getCap() == SkPaint::kSquare_Cap) { 1045 return nullptr; 1046 } 1047 break; 1048 case SkStrokeRec::kHairline_Style: 1049 // Hairline only supports butt cap. Round caps could be emulated by slightly 1050 // extending the angle range if we ever care to. 1051 if (arcParams->fUseCenter || stroke.getCap() != SkPaint::kButt_Cap) { 1052 return nullptr; 1053 } 1054 break; 1055 } 1056 } 1057 return Helper::FactoryHelper<CircleOp>(context, std::move(paint), viewMatrix, center, 1058 radius, style, arcParams); 1059 } 1060 1061 CircleOp(GrProcessorSet* processorSet, const SkPMColor4f& color, 1062 const SkMatrix& viewMatrix, SkPoint center, SkScalar radius, const GrStyle& style, 1063 const ArcParams* arcParams) 1064 : GrMeshDrawOp(ClassID()) 1065 , fHelper(processorSet, GrAAType::kCoverage) { 1066 const SkStrokeRec& stroke = style.strokeRec(); 1067 SkStrokeRec::Style recStyle = stroke.getStyle(); 1068 1069 fRoundCaps = false; 1070 1071 viewMatrix.mapPoints(¢er, 1); 1072 radius = viewMatrix.mapRadius(radius); 1073 SkScalar strokeWidth = viewMatrix.mapRadius(stroke.getWidth()); 1074 1075 bool isStrokeOnly = 1076 SkStrokeRec::kStroke_Style == recStyle || SkStrokeRec::kHairline_Style == recStyle; 1077 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == recStyle; 1078 1079 SkScalar innerRadius = -SK_ScalarHalf; 1080 SkScalar outerRadius = radius; 1081 SkScalar halfWidth = 0; 1082 if (hasStroke) { 1083 if (SkScalarNearlyZero(strokeWidth)) { 1084 halfWidth = SK_ScalarHalf; 1085 } else { 1086 halfWidth = SkScalarHalf(strokeWidth); 1087 } 1088 1089 outerRadius += halfWidth; 1090 if (isStrokeOnly) { 1091 innerRadius = radius - halfWidth; 1092 } 1093 } 1094 1095 // The radii are outset for two reasons. First, it allows the shader to simply perform 1096 // simpler computation because the computed alpha is zero, rather than 50%, at the radius. 1097 // Second, the outer radius is used to compute the verts of the bounding box that is 1098 // rendered and the outset ensures the box will cover all partially covered by the circle. 1099 outerRadius += SK_ScalarHalf; 1100 innerRadius -= SK_ScalarHalf; 1101 bool stroked = isStrokeOnly && innerRadius > 0.0f; 1102 fViewMatrixIfUsingLocalCoords = viewMatrix; 1103 1104 // This makes every point fully inside the intersection plane. 1105 static constexpr SkScalar kUnusedIsectPlane[] = {0.f, 0.f, 1.f}; 1106 // This makes every point fully outside the union plane. 1107 static constexpr SkScalar kUnusedUnionPlane[] = {0.f, 0.f, 0.f}; 1108 static constexpr SkPoint kUnusedRoundCaps[] = {{1e10f, 1e10f}, {1e10f, 1e10f}}; 1109 SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius, 1110 center.fX + outerRadius, center.fY + outerRadius); 1111 if (arcParams) { 1112 // The shader operates in a space where the circle is translated to be centered at the 1113 // origin. Here we compute points on the unit circle at the starting and ending angles. 1114 SkPoint startPoint, stopPoint; 1115 startPoint.fY = SkScalarSin(arcParams->fStartAngleRadians); 1116 startPoint.fX = SkScalarCos(arcParams->fStartAngleRadians); 1117 SkScalar endAngle = arcParams->fStartAngleRadians + arcParams->fSweepAngleRadians; 1118 stopPoint.fY = SkScalarSin(endAngle); 1119 stopPoint.fX = SkScalarCos(endAngle); 1120 1121 // Adjust the start and end points based on the view matrix (to handle rotated arcs) 1122 startPoint = viewMatrix.mapVector(startPoint.fX, startPoint.fY); 1123 stopPoint = viewMatrix.mapVector(stopPoint.fX, stopPoint.fY); 1124 startPoint.normalize(); 1125 stopPoint.normalize(); 1126 1127 // We know the matrix is a similarity here. Detect mirroring which will affect how we 1128 // should orient the clip planes for arcs. 1129 SkASSERT(viewMatrix.isSimilarity()); 1130 auto upperLeftDet = viewMatrix.getScaleX()*viewMatrix.getScaleY() - 1131 viewMatrix.getSkewX() *viewMatrix.getSkewY(); 1132 if (upperLeftDet < 0) { 1133 std::swap(startPoint, stopPoint); 1134 } 1135 1136 fRoundCaps = style.strokeRec().getWidth() > 0 && 1137 style.strokeRec().getCap() == SkPaint::kRound_Cap; 1138 SkPoint roundCaps[2]; 1139 if (fRoundCaps) { 1140 // Compute the cap center points in the normalized space. 1141 SkScalar midRadius = (innerRadius + outerRadius) / (2 * outerRadius); 1142 roundCaps[0] = startPoint * midRadius; 1143 roundCaps[1] = stopPoint * midRadius; 1144 } else { 1145 roundCaps[0] = kUnusedRoundCaps[0]; 1146 roundCaps[1] = kUnusedRoundCaps[1]; 1147 } 1148 1149 // Like a fill without useCenter, butt-cap stroke can be implemented by clipping against 1150 // radial lines. We treat round caps the same way, but tack coverage of circles at the 1151 // center of the butts. 1152 // However, in both cases we have to be careful about the half-circle. 1153 // case. In that case the two radial lines are equal and so that edge gets clipped 1154 // twice. Since the shared edge goes through the center we fall back on the !useCenter 1155 // case. 1156 auto absSweep = SkScalarAbs(arcParams->fSweepAngleRadians); 1157 bool useCenter = (arcParams->fUseCenter || isStrokeOnly) && 1158 !SkScalarNearlyEqual(absSweep, SK_ScalarPI); 1159 if (useCenter) { 1160 SkVector norm0 = {startPoint.fY, -startPoint.fX}; 1161 SkVector norm1 = {stopPoint.fY, -stopPoint.fX}; 1162 // This ensures that norm0 is always the clockwise plane, and norm1 is CCW. 1163 if (arcParams->fSweepAngleRadians < 0) { 1164 std::swap(norm0, norm1); 1165 } 1166 norm0.negate(); 1167 fClipPlane = true; 1168 if (absSweep > SK_ScalarPI) { 1169 fCircles.emplace_back(Circle{ 1170 color, 1171 innerRadius, 1172 outerRadius, 1173 {norm0.fX, norm0.fY, 0.5f}, 1174 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]}, 1175 {norm1.fX, norm1.fY, 0.5f}, 1176 {roundCaps[0], roundCaps[1]}, 1177 devBounds, 1178 stroked}); 1179 fClipPlaneIsect = false; 1180 fClipPlaneUnion = true; 1181 } else { 1182 fCircles.emplace_back(Circle{ 1183 color, 1184 innerRadius, 1185 outerRadius, 1186 {norm0.fX, norm0.fY, 0.5f}, 1187 {norm1.fX, norm1.fY, 0.5f}, 1188 {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]}, 1189 {roundCaps[0], roundCaps[1]}, 1190 devBounds, 1191 stroked}); 1192 fClipPlaneIsect = true; 1193 fClipPlaneUnion = false; 1194 } 1195 } else { 1196 // We clip to a secant of the original circle. 1197 startPoint.scale(radius); 1198 stopPoint.scale(radius); 1199 SkVector norm = {startPoint.fY - stopPoint.fY, stopPoint.fX - startPoint.fX}; 1200 norm.normalize(); 1201 if (arcParams->fSweepAngleRadians > 0) { 1202 norm.negate(); 1203 } 1204 SkScalar d = -norm.dot(startPoint) + 0.5f; 1205 1206 fCircles.emplace_back( 1207 Circle{color, 1208 innerRadius, 1209 outerRadius, 1210 {norm.fX, norm.fY, d}, 1211 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]}, 1212 {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]}, 1213 {roundCaps[0], roundCaps[1]}, 1214 devBounds, 1215 stroked}); 1216 fClipPlane = true; 1217 fClipPlaneIsect = false; 1218 fClipPlaneUnion = false; 1219 } 1220 } else { 1221 fCircles.emplace_back( 1222 Circle{color, 1223 innerRadius, 1224 outerRadius, 1225 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]}, 1226 {kUnusedIsectPlane[0], kUnusedIsectPlane[1], kUnusedIsectPlane[2]}, 1227 {kUnusedUnionPlane[0], kUnusedUnionPlane[1], kUnusedUnionPlane[2]}, 1228 {kUnusedRoundCaps[0], kUnusedRoundCaps[1]}, 1229 devBounds, 1230 stroked}); 1231 fClipPlane = false; 1232 fClipPlaneIsect = false; 1233 fClipPlaneUnion = false; 1234 } 1235 // Use the original radius and stroke radius for the bounds so that it does not include the 1236 // AA bloat. 1237 radius += halfWidth; 1238 this->setBounds( 1239 {center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius}, 1240 HasAABloat::kYes, IsHairline::kNo); 1241 fVertCount = circle_type_to_vert_count(stroked); 1242 fIndexCount = circle_type_to_index_count(stroked); 1243 fAllFill = !stroked; 1244 } 1245 1246 const char* name() const override { return "CircleOp"; } 1247 1248 void visitProxies(const GrVisitProxyFunc& func) const override { 1249 if (fProgramInfo) { 1250 fProgramInfo->visitFPProxies(func); 1251 } else { 1252 fHelper.visitProxies(func); 1253 } 1254 } 1255 1256 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip, 1257 GrClampType clampType) override { 1258 SkPMColor4f* color = &fCircles.front().fColor; 1259 return fHelper.finalizeProcessors(caps, clip, clampType, 1260 GrProcessorAnalysisCoverage::kSingleChannel, color, 1261 &fWideColor); 1262 } 1263 1264 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 1265 1266private: 1267 GrProgramInfo* programInfo() override { return fProgramInfo; } 1268 1269 void onCreateProgramInfo(const GrCaps* caps, 1270 SkArenaAlloc* arena, 1271 const GrSurfaceProxyView& writeView, 1272 bool usesMSAASurface, 1273 GrAppliedClip&& appliedClip, 1274 const GrDstProxyView& dstProxyView, 1275 GrXferBarrierFlags renderPassXferBarriers, 1276 GrLoadOp colorLoadOp) override { 1277 SkASSERT(!usesMSAASurface); 1278 1279 SkMatrix localMatrix; 1280 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { 1281 return; 1282 } 1283 1284 GrGeometryProcessor* gp = CircleGeometryProcessor::Make(arena, !fAllFill, fClipPlane, 1285 fClipPlaneIsect, fClipPlaneUnion, 1286 fRoundCaps, fWideColor, 1287 localMatrix); 1288 1289 fProgramInfo = fHelper.createProgramInfo(caps, 1290 arena, 1291 writeView, 1292 usesMSAASurface, 1293 std::move(appliedClip), 1294 dstProxyView, 1295 gp, 1296 GrPrimitiveType::kTriangles, 1297 renderPassXferBarriers, 1298 colorLoadOp); 1299 } 1300 1301 void onPrepareDraws(GrMeshDrawTarget* target) override { 1302 if (!fProgramInfo) { 1303 this->createProgramInfo(target); 1304 if (!fProgramInfo) { 1305 return; 1306 } 1307 } 1308 1309 sk_sp<const GrBuffer> vertexBuffer; 1310 int firstVertex; 1311 VertexWriter vertices{target->makeVertexSpace(fProgramInfo->geomProc().vertexStride(), 1312 fVertCount, &vertexBuffer, &firstVertex)}; 1313 if (!vertices) { 1314 SkDebugf("Could not allocate vertices\n"); 1315 return; 1316 } 1317 1318 sk_sp<const GrBuffer> indexBuffer = nullptr; 1319 int firstIndex = 0; 1320 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex); 1321 if (!indices) { 1322 SkDebugf("Could not allocate indices\n"); 1323 return; 1324 } 1325 1326 int currStartVertex = 0; 1327 for (const auto& circle : fCircles) { 1328 SkScalar innerRadius = circle.fInnerRadius; 1329 SkScalar outerRadius = circle.fOuterRadius; 1330 GrVertexColor color(circle.fColor, fWideColor); 1331 const SkRect& bounds = circle.fDevBounds; 1332 1333 // The inner radius in the vertex data must be specified in normalized space. 1334 innerRadius = innerRadius / outerRadius; 1335 SkPoint radii = { outerRadius, innerRadius }; 1336 1337 SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY()); 1338 SkScalar halfWidth = 0.5f * bounds.width(); 1339 1340 SkVector geoClipPlane = { 0, 0 }; 1341 SkScalar offsetClipDist = SK_Scalar1; 1342 if (!circle.fStroked && fClipPlane && fClipPlaneIsect && 1343 (circle.fClipPlane[0] * circle.fIsectPlane[0] + 1344 circle.fClipPlane[1] * circle.fIsectPlane[1]) < 0.0f) { 1345 // Acute arc. Clip the vertices to the perpendicular half-plane. We've constructed 1346 // fClipPlane to be clockwise, and fISectPlane to be CCW, so we can can rotate them 1347 // each 90 degrees to point "out", then average them. We back off by 1/2 pixel so 1348 // the AA can extend just past the center of the circle. 1349 geoClipPlane.set(circle.fClipPlane[1] - circle.fIsectPlane[1], 1350 circle.fIsectPlane[0] - circle.fClipPlane[0]); 1351 SkAssertResult(geoClipPlane.normalize()); 1352 offsetClipDist = 0.5f / halfWidth; 1353 } 1354 1355 for (int i = 0; i < 8; ++i) { 1356 // This clips the normalized offset to the half-plane we computed above. Then we 1357 // compute the vertex position from this. 1358 SkScalar dist = std::min(kOctagonOuter[i].dot(geoClipPlane) + offsetClipDist, 0.0f); 1359 SkVector offset = kOctagonOuter[i] - geoClipPlane * dist; 1360 vertices << (center + offset * halfWidth) 1361 << color 1362 << offset 1363 << radii; 1364 if (fClipPlane) { 1365 vertices << circle.fClipPlane; 1366 } 1367 if (fClipPlaneIsect) { 1368 vertices << circle.fIsectPlane; 1369 } 1370 if (fClipPlaneUnion) { 1371 vertices << circle.fUnionPlane; 1372 } 1373 if (fRoundCaps) { 1374 vertices << circle.fRoundCapCenters; 1375 } 1376 } 1377 1378 if (circle.fStroked) { 1379 // compute the inner ring 1380 1381 for (int i = 0; i < 8; ++i) { 1382 vertices << (center + kOctagonInner[i] * circle.fInnerRadius) 1383 << color 1384 << kOctagonInner[i] * innerRadius 1385 << radii; 1386 if (fClipPlane) { 1387 vertices << circle.fClipPlane; 1388 } 1389 if (fClipPlaneIsect) { 1390 vertices << circle.fIsectPlane; 1391 } 1392 if (fClipPlaneUnion) { 1393 vertices << circle.fUnionPlane; 1394 } 1395 if (fRoundCaps) { 1396 vertices << circle.fRoundCapCenters; 1397 } 1398 } 1399 } else { 1400 // filled 1401 vertices << center << color << SkPoint::Make(0, 0) << radii; 1402 if (fClipPlane) { 1403 vertices << circle.fClipPlane; 1404 } 1405 if (fClipPlaneIsect) { 1406 vertices << circle.fIsectPlane; 1407 } 1408 if (fClipPlaneUnion) { 1409 vertices << circle.fUnionPlane; 1410 } 1411 if (fRoundCaps) { 1412 vertices << circle.fRoundCapCenters; 1413 } 1414 } 1415 1416 const uint16_t* primIndices = circle_type_to_indices(circle.fStroked); 1417 const int primIndexCount = circle_type_to_index_count(circle.fStroked); 1418 for (int i = 0; i < primIndexCount; ++i) { 1419 *indices++ = primIndices[i] + currStartVertex; 1420 } 1421 1422 currStartVertex += circle_type_to_vert_count(circle.fStroked); 1423 } 1424 1425 fMesh = target->allocMesh(); 1426 fMesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1, 1427 GrPrimitiveRestart::kNo, std::move(vertexBuffer), firstVertex); 1428 } 1429 1430 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { 1431 if (!fProgramInfo || !fMesh) { 1432 return; 1433 } 1434 1435 flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds); 1436 flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline()); 1437 flushState->drawMesh(*fMesh); 1438 } 1439 1440 CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override { 1441 CircleOp* that = t->cast<CircleOp>(); 1442 1443 // can only represent 65535 unique vertices with 16-bit indices 1444 if (fVertCount + that->fVertCount > 65536) { 1445 return CombineResult::kCannotCombine; 1446 } 1447 1448 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 1449 return CombineResult::kCannotCombine; 1450 } 1451 1452 if (fHelper.usesLocalCoords() && 1453 !SkMatrixPriv::CheapEqual(fViewMatrixIfUsingLocalCoords, 1454 that->fViewMatrixIfUsingLocalCoords)) { 1455 return CombineResult::kCannotCombine; 1456 } 1457 1458 // Because we've set up the ops that don't use the planes with noop values 1459 // we can just accumulate used planes by later ops. 1460 fClipPlane |= that->fClipPlane; 1461 fClipPlaneIsect |= that->fClipPlaneIsect; 1462 fClipPlaneUnion |= that->fClipPlaneUnion; 1463 fRoundCaps |= that->fRoundCaps; 1464 fWideColor |= that->fWideColor; 1465 1466 fCircles.push_back_n(that->fCircles.count(), that->fCircles.begin()); 1467 fVertCount += that->fVertCount; 1468 fIndexCount += that->fIndexCount; 1469 fAllFill = fAllFill && that->fAllFill; 1470 return CombineResult::kMerged; 1471 } 1472 1473#if GR_TEST_UTILS 1474 SkString onDumpInfo() const override { 1475 SkString string; 1476 for (int i = 0; i < fCircles.count(); ++i) { 1477 string.appendf( 1478 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]," 1479 "InnerRad: %.2f, OuterRad: %.2f\n", 1480 fCircles[i].fColor.toBytes_RGBA(), fCircles[i].fDevBounds.fLeft, 1481 fCircles[i].fDevBounds.fTop, fCircles[i].fDevBounds.fRight, 1482 fCircles[i].fDevBounds.fBottom, fCircles[i].fInnerRadius, 1483 fCircles[i].fOuterRadius); 1484 } 1485 string += fHelper.dumpInfo(); 1486 return string; 1487 } 1488#endif 1489 1490 struct Circle { 1491 SkPMColor4f fColor; 1492 SkScalar fInnerRadius; 1493 SkScalar fOuterRadius; 1494 SkScalar fClipPlane[3]; 1495 SkScalar fIsectPlane[3]; 1496 SkScalar fUnionPlane[3]; 1497 SkPoint fRoundCapCenters[2]; 1498 SkRect fDevBounds; 1499 bool fStroked; 1500 }; 1501 1502 SkMatrix fViewMatrixIfUsingLocalCoords; 1503 Helper fHelper; 1504 SkSTArray<1, Circle, true> fCircles; 1505 int fVertCount; 1506 int fIndexCount; 1507 bool fAllFill; 1508 bool fClipPlane; 1509 bool fClipPlaneIsect; 1510 bool fClipPlaneUnion; 1511 bool fRoundCaps; 1512 bool fWideColor; 1513 1514 GrSimpleMesh* fMesh = nullptr; 1515 GrProgramInfo* fProgramInfo = nullptr; 1516 1517 using INHERITED = GrMeshDrawOp; 1518}; 1519 1520class ButtCapDashedCircleOp final : public GrMeshDrawOp { 1521private: 1522 using Helper = GrSimpleMeshDrawOpHelper; 1523 1524public: 1525 DEFINE_OP_CLASS_ID 1526 1527 static GrOp::Owner Make(GrRecordingContext* context, 1528 GrPaint&& paint, 1529 const SkMatrix& viewMatrix, 1530 SkPoint center, 1531 SkScalar radius, 1532 SkScalar strokeWidth, 1533 SkScalar startAngle, 1534 SkScalar onAngle, 1535 SkScalar offAngle, 1536 SkScalar phaseAngle) { 1537 SkASSERT(circle_stays_circle(viewMatrix)); 1538 SkASSERT(strokeWidth < 2 * radius); 1539 return Helper::FactoryHelper<ButtCapDashedCircleOp>(context, std::move(paint), viewMatrix, 1540 center, radius, strokeWidth, startAngle, 1541 onAngle, offAngle, phaseAngle); 1542 } 1543 1544 ButtCapDashedCircleOp(GrProcessorSet* processorSet, const SkPMColor4f& color, 1545 const SkMatrix& viewMatrix, SkPoint center, SkScalar radius, 1546 SkScalar strokeWidth, SkScalar startAngle, SkScalar onAngle, 1547 SkScalar offAngle, SkScalar phaseAngle) 1548 : GrMeshDrawOp(ClassID()) 1549 , fHelper(processorSet, GrAAType::kCoverage) { 1550 SkASSERT(circle_stays_circle(viewMatrix)); 1551 viewMatrix.mapPoints(¢er, 1); 1552 radius = viewMatrix.mapRadius(radius); 1553 strokeWidth = viewMatrix.mapRadius(strokeWidth); 1554 1555 // Determine the angle where the circle starts in device space and whether its orientation 1556 // has been reversed. 1557 SkVector start; 1558 bool reflection; 1559 if (!startAngle) { 1560 start = {1, 0}; 1561 } else { 1562 start.fY = SkScalarSin(startAngle); 1563 start.fX = SkScalarCos(startAngle); 1564 } 1565 viewMatrix.mapVectors(&start, 1); 1566 startAngle = SkScalarATan2(start.fY, start.fX); 1567 reflection = (viewMatrix.getScaleX() * viewMatrix.getScaleY() - 1568 viewMatrix.getSkewX() * viewMatrix.getSkewY()) < 0; 1569 1570 auto totalAngle = onAngle + offAngle; 1571 phaseAngle = SkScalarMod(phaseAngle + totalAngle / 2, totalAngle) - totalAngle / 2; 1572 1573 SkScalar halfWidth = 0; 1574 if (SkScalarNearlyZero(strokeWidth)) { 1575 halfWidth = SK_ScalarHalf; 1576 } else { 1577 halfWidth = SkScalarHalf(strokeWidth); 1578 } 1579 1580 SkScalar outerRadius = radius + halfWidth; 1581 SkScalar innerRadius = radius - halfWidth; 1582 1583 // The radii are outset for two reasons. First, it allows the shader to simply perform 1584 // simpler computation because the computed alpha is zero, rather than 50%, at the radius. 1585 // Second, the outer radius is used to compute the verts of the bounding box that is 1586 // rendered and the outset ensures the box will cover all partially covered by the circle. 1587 outerRadius += SK_ScalarHalf; 1588 innerRadius -= SK_ScalarHalf; 1589 fViewMatrixIfUsingLocalCoords = viewMatrix; 1590 1591 SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius, 1592 center.fX + outerRadius, center.fY + outerRadius); 1593 1594 // We store whether there is a reflection as a negative total angle. 1595 if (reflection) { 1596 totalAngle = -totalAngle; 1597 } 1598 fCircles.push_back(Circle{ 1599 color, 1600 outerRadius, 1601 innerRadius, 1602 onAngle, 1603 totalAngle, 1604 startAngle, 1605 phaseAngle, 1606 devBounds 1607 }); 1608 // Use the original radius and stroke radius for the bounds so that it does not include the 1609 // AA bloat. 1610 radius += halfWidth; 1611 this->setBounds( 1612 {center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius}, 1613 HasAABloat::kYes, IsHairline::kNo); 1614 fVertCount = circle_type_to_vert_count(true); 1615 fIndexCount = circle_type_to_index_count(true); 1616 } 1617 1618 const char* name() const override { return "ButtCappedDashedCircleOp"; } 1619 1620 void visitProxies(const GrVisitProxyFunc& func) const override { 1621 if (fProgramInfo) { 1622 fProgramInfo->visitFPProxies(func); 1623 } else { 1624 fHelper.visitProxies(func); 1625 } 1626 } 1627 1628 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip, 1629 GrClampType clampType) override { 1630 SkPMColor4f* color = &fCircles.front().fColor; 1631 return fHelper.finalizeProcessors(caps, clip, clampType, 1632 GrProcessorAnalysisCoverage::kSingleChannel, color, 1633 &fWideColor); 1634 } 1635 1636 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 1637 1638private: 1639 GrProgramInfo* programInfo() override { return fProgramInfo; } 1640 1641 void onCreateProgramInfo(const GrCaps* caps, 1642 SkArenaAlloc* arena, 1643 const GrSurfaceProxyView& writeView, 1644 bool usesMSAASurface, 1645 GrAppliedClip&& appliedClip, 1646 const GrDstProxyView& dstProxyView, 1647 GrXferBarrierFlags renderPassXferBarriers, 1648 GrLoadOp colorLoadOp) override { 1649 SkASSERT(!usesMSAASurface); 1650 1651 SkMatrix localMatrix; 1652 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { 1653 return; 1654 } 1655 1656 // Setup geometry processor 1657 GrGeometryProcessor* gp = ButtCapDashedCircleGeometryProcessor::Make(arena, 1658 fWideColor, 1659 localMatrix); 1660 1661 fProgramInfo = fHelper.createProgramInfo(caps, 1662 arena, 1663 writeView, 1664 usesMSAASurface, 1665 std::move(appliedClip), 1666 dstProxyView, 1667 gp, 1668 GrPrimitiveType::kTriangles, 1669 renderPassXferBarriers, 1670 colorLoadOp); 1671 } 1672 1673 void onPrepareDraws(GrMeshDrawTarget* target) override { 1674 if (!fProgramInfo) { 1675 this->createProgramInfo(target); 1676 if (!fProgramInfo) { 1677 return; 1678 } 1679 } 1680 1681 sk_sp<const GrBuffer> vertexBuffer; 1682 int firstVertex; 1683 VertexWriter vertices{target->makeVertexSpace(fProgramInfo->geomProc().vertexStride(), 1684 fVertCount, &vertexBuffer, &firstVertex)}; 1685 if (!vertices) { 1686 SkDebugf("Could not allocate vertices\n"); 1687 return; 1688 } 1689 1690 sk_sp<const GrBuffer> indexBuffer; 1691 int firstIndex = 0; 1692 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex); 1693 if (!indices) { 1694 SkDebugf("Could not allocate indices\n"); 1695 return; 1696 } 1697 1698 int currStartVertex = 0; 1699 for (const auto& circle : fCircles) { 1700 // The inner radius in the vertex data must be specified in normalized space so that 1701 // length() can be called with smaller values to avoid precision issues with half 1702 // floats. 1703 auto normInnerRadius = circle.fInnerRadius / circle.fOuterRadius; 1704 const SkRect& bounds = circle.fDevBounds; 1705 bool reflect = false; 1706 struct { float onAngle, totalAngle, startAngle, phaseAngle; } dashParams = { 1707 circle.fOnAngle, circle.fTotalAngle, circle.fStartAngle, circle.fPhaseAngle 1708 }; 1709 if (dashParams.totalAngle < 0) { 1710 reflect = true; 1711 dashParams.totalAngle = -dashParams.totalAngle; 1712 dashParams.startAngle = -dashParams.startAngle; 1713 } 1714 1715 GrVertexColor color(circle.fColor, fWideColor); 1716 1717 // The bounding geometry for the circle is composed of an outer bounding octagon and 1718 // an inner bounded octagon. 1719 1720 // Compute the vertices of the outer octagon. 1721 SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY()); 1722 SkScalar halfWidth = 0.5f * bounds.width(); 1723 1724 auto reflectY = [=](const SkPoint& p) { 1725 return SkPoint{ p.fX, reflect ? -p.fY : p.fY }; 1726 }; 1727 1728 for (int i = 0; i < 8; ++i) { 1729 vertices << (center + kOctagonOuter[i] * halfWidth) 1730 << color 1731 << reflectY(kOctagonOuter[i]) 1732 << circle.fOuterRadius 1733 << normInnerRadius 1734 << dashParams; 1735 } 1736 1737 // Compute the vertices of the inner octagon. 1738 for (int i = 0; i < 8; ++i) { 1739 vertices << (center + kOctagonInner[i] * circle.fInnerRadius) 1740 << color 1741 << (reflectY(kOctagonInner[i]) * normInnerRadius) 1742 << circle.fOuterRadius 1743 << normInnerRadius 1744 << dashParams; 1745 } 1746 1747 const uint16_t* primIndices = circle_type_to_indices(true); 1748 const int primIndexCount = circle_type_to_index_count(true); 1749 for (int i = 0; i < primIndexCount; ++i) { 1750 *indices++ = primIndices[i] + currStartVertex; 1751 } 1752 1753 currStartVertex += circle_type_to_vert_count(true); 1754 } 1755 1756 fMesh = target->allocMesh(); 1757 fMesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1, 1758 GrPrimitiveRestart::kNo, std::move(vertexBuffer), firstVertex); 1759 } 1760 1761 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { 1762 if (!fProgramInfo || !fMesh) { 1763 return; 1764 } 1765 1766 flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds); 1767 flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline()); 1768 flushState->drawMesh(*fMesh); 1769 } 1770 1771 CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override { 1772 ButtCapDashedCircleOp* that = t->cast<ButtCapDashedCircleOp>(); 1773 1774 // can only represent 65535 unique vertices with 16-bit indices 1775 if (fVertCount + that->fVertCount > 65536) { 1776 return CombineResult::kCannotCombine; 1777 } 1778 1779 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 1780 return CombineResult::kCannotCombine; 1781 } 1782 1783 if (fHelper.usesLocalCoords() && 1784 !SkMatrixPriv::CheapEqual(fViewMatrixIfUsingLocalCoords, 1785 that->fViewMatrixIfUsingLocalCoords)) { 1786 return CombineResult::kCannotCombine; 1787 } 1788 1789 fCircles.push_back_n(that->fCircles.count(), that->fCircles.begin()); 1790 fVertCount += that->fVertCount; 1791 fIndexCount += that->fIndexCount; 1792 fWideColor |= that->fWideColor; 1793 return CombineResult::kMerged; 1794 } 1795 1796#if GR_TEST_UTILS 1797 SkString onDumpInfo() const override { 1798 SkString string; 1799 for (int i = 0; i < fCircles.count(); ++i) { 1800 string.appendf( 1801 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]," 1802 "InnerRad: %.2f, OuterRad: %.2f, OnAngle: %.2f, TotalAngle: %.2f, " 1803 "Phase: %.2f\n", 1804 fCircles[i].fColor.toBytes_RGBA(), fCircles[i].fDevBounds.fLeft, 1805 fCircles[i].fDevBounds.fTop, fCircles[i].fDevBounds.fRight, 1806 fCircles[i].fDevBounds.fBottom, fCircles[i].fInnerRadius, 1807 fCircles[i].fOuterRadius, fCircles[i].fOnAngle, fCircles[i].fTotalAngle, 1808 fCircles[i].fPhaseAngle); 1809 } 1810 string += fHelper.dumpInfo(); 1811 return string; 1812 } 1813#endif 1814 1815 struct Circle { 1816 SkPMColor4f fColor; 1817 SkScalar fOuterRadius; 1818 SkScalar fInnerRadius; 1819 SkScalar fOnAngle; 1820 SkScalar fTotalAngle; 1821 SkScalar fStartAngle; 1822 SkScalar fPhaseAngle; 1823 SkRect fDevBounds; 1824 }; 1825 1826 SkMatrix fViewMatrixIfUsingLocalCoords; 1827 Helper fHelper; 1828 SkSTArray<1, Circle, true> fCircles; 1829 int fVertCount; 1830 int fIndexCount; 1831 bool fWideColor; 1832 1833 GrSimpleMesh* fMesh = nullptr; 1834 GrProgramInfo* fProgramInfo = nullptr; 1835 1836 using INHERITED = GrMeshDrawOp; 1837}; 1838 1839/////////////////////////////////////////////////////////////////////////////// 1840 1841class EllipseOp : public GrMeshDrawOp { 1842private: 1843 using Helper = GrSimpleMeshDrawOpHelper; 1844 1845 struct DeviceSpaceParams { 1846 SkPoint fCenter; 1847 SkScalar fXRadius; 1848 SkScalar fYRadius; 1849 SkScalar fInnerXRadius; 1850 SkScalar fInnerYRadius; 1851 }; 1852 1853public: 1854 DEFINE_OP_CLASS_ID 1855 1856 static GrOp::Owner Make(GrRecordingContext* context, 1857 GrPaint&& paint, 1858 const SkMatrix& viewMatrix, 1859 const SkRect& ellipse, 1860 const SkStrokeRec& stroke) { 1861 DeviceSpaceParams params; 1862 // do any matrix crunching before we reset the draw state for device coords 1863 params.fCenter = SkPoint::Make(ellipse.centerX(), ellipse.centerY()); 1864 viewMatrix.mapPoints(¶ms.fCenter, 1); 1865 SkScalar ellipseXRadius = SkScalarHalf(ellipse.width()); 1866 SkScalar ellipseYRadius = SkScalarHalf(ellipse.height()); 1867 params.fXRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * ellipseXRadius + 1868 viewMatrix[SkMatrix::kMSkewX] * ellipseYRadius); 1869 params.fYRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewY] * ellipseXRadius + 1870 viewMatrix[SkMatrix::kMScaleY] * ellipseYRadius); 1871 1872 // do (potentially) anisotropic mapping of stroke 1873 SkVector scaledStroke; 1874 SkScalar strokeWidth = stroke.getWidth(); 1875 scaledStroke.fX = SkScalarAbs( 1876 strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY])); 1877 scaledStroke.fY = SkScalarAbs( 1878 strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY])); 1879 1880 SkStrokeRec::Style style = stroke.getStyle(); 1881 bool isStrokeOnly = 1882 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style; 1883 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style; 1884 1885 params.fInnerXRadius = 0; 1886 params.fInnerYRadius = 0; 1887 if (hasStroke) { 1888 if (SkScalarNearlyZero(scaledStroke.length())) { 1889 scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf); 1890 } else { 1891 scaledStroke.scale(SK_ScalarHalf); 1892 } 1893 1894 // we only handle thick strokes for near-circular ellipses 1895 if (scaledStroke.length() > SK_ScalarHalf && 1896 (0.5f * params.fXRadius > params.fYRadius || 1897 0.5f * params.fYRadius > params.fXRadius)) { 1898 return nullptr; 1899 } 1900 1901 // we don't handle it if curvature of the stroke is less than curvature of the ellipse 1902 if (scaledStroke.fX * (params.fXRadius * params.fYRadius) < 1903 (scaledStroke.fY * scaledStroke.fY) * params.fXRadius || 1904 scaledStroke.fY * (params.fXRadius * params.fXRadius) < 1905 (scaledStroke.fX * scaledStroke.fX) * params.fYRadius) { 1906 return nullptr; 1907 } 1908 1909 // this is legit only if scale & translation (which should be the case at the moment) 1910 if (isStrokeOnly) { 1911 params.fInnerXRadius = params.fXRadius - scaledStroke.fX; 1912 params.fInnerYRadius = params.fYRadius - scaledStroke.fY; 1913 } 1914 1915 params.fXRadius += scaledStroke.fX; 1916 params.fYRadius += scaledStroke.fY; 1917 } 1918 1919 // For large ovals with low precision floats, we fall back to the path renderer. 1920 // To compute the AA at the edge we divide by the gradient, which is clamped to a 1921 // minimum value to avoid divides by zero. With large ovals and low precision this 1922 // leads to blurring at the edge of the oval. 1923 const SkScalar kMaxOvalRadius = 16384; 1924 if (!context->priv().caps()->shaderCaps()->floatIs32Bits() && 1925 (params.fXRadius >= kMaxOvalRadius || params.fYRadius >= kMaxOvalRadius)) { 1926 return nullptr; 1927 } 1928 1929 return Helper::FactoryHelper<EllipseOp>(context, std::move(paint), viewMatrix, 1930 params, stroke); 1931 } 1932 1933 EllipseOp(GrProcessorSet* processorSet, const SkPMColor4f& color, 1934 const SkMatrix& viewMatrix, const DeviceSpaceParams& params, 1935 const SkStrokeRec& stroke) 1936 : INHERITED(ClassID()) 1937 , fHelper(processorSet, GrAAType::kCoverage) 1938 , fUseScale(false) { 1939 SkStrokeRec::Style style = stroke.getStyle(); 1940 bool isStrokeOnly = 1941 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style; 1942 1943 fEllipses.emplace_back(Ellipse{color, params.fXRadius, params.fYRadius, 1944 params.fInnerXRadius, params.fInnerYRadius, 1945 SkRect::MakeLTRB(params.fCenter.fX - params.fXRadius, 1946 params.fCenter.fY - params.fYRadius, 1947 params.fCenter.fX + params.fXRadius, 1948 params.fCenter.fY + params.fYRadius)}); 1949 1950 this->setBounds(fEllipses.back().fDevBounds, HasAABloat::kYes, IsHairline::kNo); 1951 1952 fStroked = isStrokeOnly && params.fInnerXRadius > 0 && params.fInnerYRadius > 0; 1953 fViewMatrixIfUsingLocalCoords = viewMatrix; 1954 } 1955 1956 const char* name() const override { return "EllipseOp"; } 1957 1958 void visitProxies(const GrVisitProxyFunc& func) const override { 1959 if (fProgramInfo) { 1960 fProgramInfo->visitFPProxies(func); 1961 } else { 1962 fHelper.visitProxies(func); 1963 } 1964 } 1965 1966 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip, 1967 GrClampType clampType) override { 1968 fUseScale = !caps.shaderCaps()->floatIs32Bits() && 1969 !caps.shaderCaps()->hasLowFragmentPrecision(); 1970 SkPMColor4f* color = &fEllipses.front().fColor; 1971 return fHelper.finalizeProcessors(caps, clip, clampType, 1972 GrProcessorAnalysisCoverage::kSingleChannel, color, 1973 &fWideColor); 1974 } 1975 1976 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 1977 1978private: 1979 GrProgramInfo* programInfo() override { return fProgramInfo; } 1980 1981 void onCreateProgramInfo(const GrCaps* caps, 1982 SkArenaAlloc* arena, 1983 const GrSurfaceProxyView& writeView, 1984 bool usesMSAASurface, 1985 GrAppliedClip&& appliedClip, 1986 const GrDstProxyView& dstProxyView, 1987 GrXferBarrierFlags renderPassXferBarriers, 1988 GrLoadOp colorLoadOp) override { 1989 SkMatrix localMatrix; 1990 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { 1991 return; 1992 } 1993 1994 GrGeometryProcessor* gp = EllipseGeometryProcessor::Make(arena, fStroked, fWideColor, 1995 fUseScale, localMatrix); 1996 1997 fProgramInfo = fHelper.createProgramInfo(caps, 1998 arena, 1999 writeView, 2000 usesMSAASurface, 2001 std::move(appliedClip), 2002 dstProxyView, 2003 gp, 2004 GrPrimitiveType::kTriangles, 2005 renderPassXferBarriers, 2006 colorLoadOp); 2007 } 2008 2009 void onPrepareDraws(GrMeshDrawTarget* target) override { 2010 if (!fProgramInfo) { 2011 this->createProgramInfo(target); 2012 if (!fProgramInfo) { 2013 return; 2014 } 2015 } 2016 2017 QuadHelper helper(target, fProgramInfo->geomProc().vertexStride(), fEllipses.count()); 2018 VertexWriter verts{helper.vertices()}; 2019 if (!verts) { 2020 SkDebugf("Could not allocate vertices\n"); 2021 return; 2022 } 2023 2024 // On MSAA, bloat enough to guarantee any pixel that might be touched by the ellipse has 2025 // full sample coverage. 2026 float aaBloat = target->usesMSAASurface() ? SK_ScalarSqrt2 : .5f; 2027 2028 for (const auto& ellipse : fEllipses) { 2029 GrVertexColor color(ellipse.fColor, fWideColor); 2030 SkScalar xRadius = ellipse.fXRadius; 2031 SkScalar yRadius = ellipse.fYRadius; 2032 2033 // Compute the reciprocals of the radii here to save time in the shader 2034 struct { float xOuter, yOuter, xInner, yInner; } invRadii = { 2035 SkScalarInvert(xRadius), 2036 SkScalarInvert(yRadius), 2037 SkScalarInvert(ellipse.fInnerXRadius), 2038 SkScalarInvert(ellipse.fInnerYRadius) 2039 }; 2040 SkScalar xMaxOffset = xRadius + aaBloat; 2041 SkScalar yMaxOffset = yRadius + aaBloat; 2042 2043 if (!fStroked) { 2044 // For filled ellipses we map a unit circle in the vertex attributes rather than 2045 // computing an ellipse and modifying that distance, so we normalize to 1 2046 xMaxOffset /= xRadius; 2047 yMaxOffset /= yRadius; 2048 } 2049 2050 // The inner radius in the vertex data must be specified in normalized space. 2051 verts.writeQuad(VertexWriter::TriStripFromRect( 2052 ellipse.fDevBounds.makeOutset(aaBloat, aaBloat)), 2053 color, 2054 origin_centered_tri_strip(xMaxOffset, yMaxOffset), 2055 VertexWriter::If(fUseScale, std::max(xRadius, yRadius)), 2056 invRadii); 2057 } 2058 fMesh = helper.mesh(); 2059 } 2060 2061 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { 2062 if (!fProgramInfo || !fMesh) { 2063 return; 2064 } 2065 2066 flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds); 2067 flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline()); 2068 flushState->drawMesh(*fMesh); 2069 } 2070 2071 CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override { 2072 EllipseOp* that = t->cast<EllipseOp>(); 2073 2074 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 2075 return CombineResult::kCannotCombine; 2076 } 2077 2078 if (fStroked != that->fStroked) { 2079 return CombineResult::kCannotCombine; 2080 } 2081 2082 if (fHelper.usesLocalCoords() && 2083 !SkMatrixPriv::CheapEqual(fViewMatrixIfUsingLocalCoords, 2084 that->fViewMatrixIfUsingLocalCoords)) { 2085 return CombineResult::kCannotCombine; 2086 } 2087 2088 fEllipses.push_back_n(that->fEllipses.count(), that->fEllipses.begin()); 2089 fWideColor |= that->fWideColor; 2090 return CombineResult::kMerged; 2091 } 2092 2093#if GR_TEST_UTILS 2094 SkString onDumpInfo() const override { 2095 SkString string = SkStringPrintf("Stroked: %d\n", fStroked); 2096 for (const auto& geo : fEllipses) { 2097 string.appendf( 2098 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], " 2099 "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n", 2100 geo.fColor.toBytes_RGBA(), geo.fDevBounds.fLeft, geo.fDevBounds.fTop, 2101 geo.fDevBounds.fRight, geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius, 2102 geo.fInnerXRadius, geo.fInnerYRadius); 2103 } 2104 string += fHelper.dumpInfo(); 2105 return string; 2106 } 2107#endif 2108 2109 struct Ellipse { 2110 SkPMColor4f fColor; 2111 SkScalar fXRadius; 2112 SkScalar fYRadius; 2113 SkScalar fInnerXRadius; 2114 SkScalar fInnerYRadius; 2115 SkRect fDevBounds; 2116 }; 2117 2118 SkMatrix fViewMatrixIfUsingLocalCoords; 2119 Helper fHelper; 2120 bool fStroked; 2121 bool fWideColor; 2122 bool fUseScale; 2123 SkSTArray<1, Ellipse, true> fEllipses; 2124 2125 GrSimpleMesh* fMesh = nullptr; 2126 GrProgramInfo* fProgramInfo = nullptr; 2127 2128 using INHERITED = GrMeshDrawOp; 2129}; 2130 2131///////////////////////////////////////////////////////////////////////////////////////////////// 2132 2133class DIEllipseOp : public GrMeshDrawOp { 2134private: 2135 using Helper = GrSimpleMeshDrawOpHelper; 2136 2137 struct DeviceSpaceParams { 2138 SkPoint fCenter; 2139 SkScalar fXRadius; 2140 SkScalar fYRadius; 2141 SkScalar fInnerXRadius; 2142 SkScalar fInnerYRadius; 2143 DIEllipseStyle fStyle; 2144 }; 2145 2146public: 2147 DEFINE_OP_CLASS_ID 2148 2149 static GrOp::Owner Make(GrRecordingContext* context, 2150 GrPaint&& paint, 2151 const SkMatrix& viewMatrix, 2152 const SkRect& ellipse, 2153 const SkStrokeRec& stroke) { 2154 DeviceSpaceParams params; 2155 params.fCenter = SkPoint::Make(ellipse.centerX(), ellipse.centerY()); 2156 params.fXRadius = SkScalarHalf(ellipse.width()); 2157 params.fYRadius = SkScalarHalf(ellipse.height()); 2158 2159 SkStrokeRec::Style style = stroke.getStyle(); 2160 params.fStyle = (SkStrokeRec::kStroke_Style == style) 2161 ? DIEllipseStyle::kStroke 2162 : (SkStrokeRec::kHairline_Style == style) 2163 ? DIEllipseStyle::kHairline 2164 : DIEllipseStyle::kFill; 2165 2166 params.fInnerXRadius = 0; 2167 params.fInnerYRadius = 0; 2168 if (SkStrokeRec::kFill_Style != style && SkStrokeRec::kHairline_Style != style) { 2169 SkScalar strokeWidth = stroke.getWidth(); 2170 2171 if (SkScalarNearlyZero(strokeWidth)) { 2172 strokeWidth = SK_ScalarHalf; 2173 } else { 2174 strokeWidth *= SK_ScalarHalf; 2175 } 2176 2177 // we only handle thick strokes for near-circular ellipses 2178 if (strokeWidth > SK_ScalarHalf && 2179 (SK_ScalarHalf * params.fXRadius > params.fYRadius || 2180 SK_ScalarHalf * params.fYRadius > params.fXRadius)) { 2181 return nullptr; 2182 } 2183 2184 // we don't handle it if curvature of the stroke is less than curvature of the ellipse 2185 if (strokeWidth * (params.fYRadius * params.fYRadius) < 2186 (strokeWidth * strokeWidth) * params.fXRadius) { 2187 return nullptr; 2188 } 2189 if (strokeWidth * (params.fXRadius * params.fXRadius) < 2190 (strokeWidth * strokeWidth) * params.fYRadius) { 2191 return nullptr; 2192 } 2193 2194 // set inner radius (if needed) 2195 if (SkStrokeRec::kStroke_Style == style) { 2196 params.fInnerXRadius = params.fXRadius - strokeWidth; 2197 params.fInnerYRadius = params.fYRadius - strokeWidth; 2198 } 2199 2200 params.fXRadius += strokeWidth; 2201 params.fYRadius += strokeWidth; 2202 } 2203 2204 // For large ovals with low precision floats, we fall back to the path renderer. 2205 // To compute the AA at the edge we divide by the gradient, which is clamped to a 2206 // minimum value to avoid divides by zero. With large ovals and low precision this 2207 // leads to blurring at the edge of the oval. 2208 const SkScalar kMaxOvalRadius = 16384; 2209 if (!context->priv().caps()->shaderCaps()->floatIs32Bits() && 2210 (params.fXRadius >= kMaxOvalRadius || params.fYRadius >= kMaxOvalRadius)) { 2211 return nullptr; 2212 } 2213 2214 if (DIEllipseStyle::kStroke == params.fStyle && 2215 (params.fInnerXRadius <= 0 || params.fInnerYRadius <= 0)) { 2216 params.fStyle = DIEllipseStyle::kFill; 2217 } 2218 return Helper::FactoryHelper<DIEllipseOp>(context, std::move(paint), params, viewMatrix); 2219 } 2220 2221 DIEllipseOp(GrProcessorSet* processorSet, const SkPMColor4f& color, 2222 const DeviceSpaceParams& params, const SkMatrix& viewMatrix) 2223 : INHERITED(ClassID()) 2224 , fHelper(processorSet, GrAAType::kCoverage) 2225 , fUseScale(false) { 2226 // This expands the outer rect so that after CTM we end up with a half-pixel border 2227 SkScalar a = viewMatrix[SkMatrix::kMScaleX]; 2228 SkScalar b = viewMatrix[SkMatrix::kMSkewX]; 2229 SkScalar c = viewMatrix[SkMatrix::kMSkewY]; 2230 SkScalar d = viewMatrix[SkMatrix::kMScaleY]; 2231 SkScalar geoDx = 1.f / SkScalarSqrt(a * a + c * c); 2232 SkScalar geoDy = 1.f / SkScalarSqrt(b * b + d * d); 2233 2234 fEllipses.emplace_back( 2235 Ellipse{viewMatrix, color, params.fXRadius, params.fYRadius, params.fInnerXRadius, 2236 params.fInnerYRadius, geoDx, geoDy, params.fStyle, 2237 SkRect::MakeLTRB(params.fCenter.fX - params.fXRadius, 2238 params.fCenter.fY - params.fYRadius, 2239 params.fCenter.fX + params.fXRadius, 2240 params.fCenter.fY + params.fYRadius)}); 2241 this->setTransformedBounds(fEllipses[0].fBounds, viewMatrix, HasAABloat::kYes, 2242 IsHairline::kNo); 2243 } 2244 2245 const char* name() const override { return "DIEllipseOp"; } 2246 2247 void visitProxies(const GrVisitProxyFunc& func) const override { 2248 if (fProgramInfo) { 2249 fProgramInfo->visitFPProxies(func); 2250 } else { 2251 fHelper.visitProxies(func); 2252 } 2253 } 2254 2255 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip, 2256 GrClampType clampType) override { 2257 fUseScale = !caps.shaderCaps()->floatIs32Bits() && 2258 !caps.shaderCaps()->hasLowFragmentPrecision(); 2259 SkPMColor4f* color = &fEllipses.front().fColor; 2260 return fHelper.finalizeProcessors(caps, clip, clampType, 2261 GrProcessorAnalysisCoverage::kSingleChannel, color, 2262 &fWideColor); 2263 } 2264 2265 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 2266 2267private: 2268 GrProgramInfo* programInfo() override { return fProgramInfo; } 2269 2270 void onCreateProgramInfo(const GrCaps* caps, 2271 SkArenaAlloc* arena, 2272 const GrSurfaceProxyView& writeView, 2273 bool usesMSAASurface, 2274 GrAppliedClip&& appliedClip, 2275 const GrDstProxyView& dstProxyView, 2276 GrXferBarrierFlags renderPassXferBarriers, 2277 GrLoadOp colorLoadOp) override { 2278 GrGeometryProcessor* gp = DIEllipseGeometryProcessor::Make(arena, fWideColor, fUseScale, 2279 this->viewMatrix(), 2280 this->style()); 2281 2282 fProgramInfo = fHelper.createProgramInfo(caps, arena, writeView, usesMSAASurface, 2283 std::move(appliedClip), dstProxyView, gp, 2284 GrPrimitiveType::kTriangles, 2285 renderPassXferBarriers, colorLoadOp); 2286 } 2287 2288 void onPrepareDraws(GrMeshDrawTarget* target) override { 2289 if (!fProgramInfo) { 2290 this->createProgramInfo(target); 2291 } 2292 2293 QuadHelper helper(target, fProgramInfo->geomProc().vertexStride(), fEllipses.count()); 2294 VertexWriter verts{helper.vertices()}; 2295 if (!verts) { 2296 return; 2297 } 2298 2299 for (const auto& ellipse : fEllipses) { 2300 GrVertexColor color(ellipse.fColor, fWideColor); 2301 SkScalar xRadius = ellipse.fXRadius; 2302 SkScalar yRadius = ellipse.fYRadius; 2303 2304 // On MSAA, bloat enough to guarantee any pixel that might be touched by the ellipse has 2305 // full sample coverage. 2306 float aaBloat = target->usesMSAASurface() ? SK_ScalarSqrt2 : .5f; 2307 SkRect drawBounds = ellipse.fBounds.makeOutset(ellipse.fGeoDx * aaBloat, 2308 ellipse.fGeoDy * aaBloat); 2309 2310 // Normalize the "outer radius" coordinates within drawBounds so that the outer edge 2311 // occurs at x^2 + y^2 == 1. 2312 float outerCoordX = drawBounds.width() / (xRadius * 2); 2313 float outerCoordY = drawBounds.height() / (yRadius * 2); 2314 2315 // By default, constructed so that inner coord is (0, 0) for all points 2316 float innerCoordX = 0; 2317 float innerCoordY = 0; 2318 2319 // ... unless we're stroked. Then normalize the "inner radius" coordinates within 2320 // drawBounds so that the inner edge occurs at x2^2 + y2^2 == 1. 2321 if (DIEllipseStyle::kStroke == this->style()) { 2322 innerCoordX = drawBounds.width() / (ellipse.fInnerXRadius * 2); 2323 innerCoordY = drawBounds.height() / (ellipse.fInnerYRadius * 2); 2324 } 2325 2326 verts.writeQuad(VertexWriter::TriStripFromRect(drawBounds), 2327 color, 2328 origin_centered_tri_strip(outerCoordX, outerCoordY), 2329 VertexWriter::If(fUseScale, std::max(xRadius, yRadius)), 2330 origin_centered_tri_strip(innerCoordX, innerCoordY)); 2331 } 2332 fMesh = helper.mesh(); 2333 } 2334 2335 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { 2336 if (!fProgramInfo || !fMesh) { 2337 return; 2338 } 2339 2340 flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds); 2341 flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline()); 2342 flushState->drawMesh(*fMesh); 2343 } 2344 2345 CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override { 2346 DIEllipseOp* that = t->cast<DIEllipseOp>(); 2347 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 2348 return CombineResult::kCannotCombine; 2349 } 2350 2351 if (this->style() != that->style()) { 2352 return CombineResult::kCannotCombine; 2353 } 2354 2355 // TODO rewrite to allow positioning on CPU 2356 if (!SkMatrixPriv::CheapEqual(this->viewMatrix(), that->viewMatrix())) { 2357 return CombineResult::kCannotCombine; 2358 } 2359 2360 fEllipses.push_back_n(that->fEllipses.count(), that->fEllipses.begin()); 2361 fWideColor |= that->fWideColor; 2362 return CombineResult::kMerged; 2363 } 2364 2365#if GR_TEST_UTILS 2366 SkString onDumpInfo() const override { 2367 SkString string; 2368 for (const auto& geo : fEllipses) { 2369 string.appendf( 2370 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], XRad: %.2f, " 2371 "YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f, GeoDX: %.2f, " 2372 "GeoDY: %.2f\n", 2373 geo.fColor.toBytes_RGBA(), geo.fBounds.fLeft, geo.fBounds.fTop, 2374 geo.fBounds.fRight, geo.fBounds.fBottom, geo.fXRadius, geo.fYRadius, 2375 geo.fInnerXRadius, geo.fInnerYRadius, geo.fGeoDx, geo.fGeoDy); 2376 } 2377 string += fHelper.dumpInfo(); 2378 return string; 2379 } 2380#endif 2381 2382 const SkMatrix& viewMatrix() const { return fEllipses[0].fViewMatrix; } 2383 DIEllipseStyle style() const { return fEllipses[0].fStyle; } 2384 2385 struct Ellipse { 2386 SkMatrix fViewMatrix; 2387 SkPMColor4f fColor; 2388 SkScalar fXRadius; 2389 SkScalar fYRadius; 2390 SkScalar fInnerXRadius; 2391 SkScalar fInnerYRadius; 2392 SkScalar fGeoDx; 2393 SkScalar fGeoDy; 2394 DIEllipseStyle fStyle; 2395 SkRect fBounds; 2396 }; 2397 2398 Helper fHelper; 2399 bool fWideColor; 2400 bool fUseScale; 2401 SkSTArray<1, Ellipse, true> fEllipses; 2402 2403 GrSimpleMesh* fMesh = nullptr; 2404 GrProgramInfo* fProgramInfo = nullptr; 2405 2406 using INHERITED = GrMeshDrawOp; 2407}; 2408 2409/////////////////////////////////////////////////////////////////////////////// 2410 2411// We have three possible cases for geometry for a roundrect. 2412// 2413// In the case of a normal fill or a stroke, we draw the roundrect as a 9-patch: 2414// ____________ 2415// |_|________|_| 2416// | | | | 2417// | | | | 2418// | | | | 2419// |_|________|_| 2420// |_|________|_| 2421// 2422// For strokes, we don't draw the center quad. 2423// 2424// For circular roundrects, in the case where the stroke width is greater than twice 2425// the corner radius (overstroke), we add additional geometry to mark out the rectangle 2426// in the center. The shared vertices are duplicated so we can set a different outer radius 2427// for the fill calculation. 2428// ____________ 2429// |_|________|_| 2430// | |\ ____ /| | 2431// | | | | | | 2432// | | |____| | | 2433// |_|/______\|_| 2434// |_|________|_| 2435// 2436// We don't draw the center quad from the fill rect in this case. 2437// 2438// For filled rrects that need to provide a distance vector we resuse the overstroke 2439// geometry but make the inner rect degenerate (either a point or a horizontal or 2440// vertical line). 2441 2442static const uint16_t gOverstrokeRRectIndices[] = { 2443 // clang-format off 2444 // overstroke quads 2445 // we place this at the beginning so that we can skip these indices when rendering normally 2446 16, 17, 19, 16, 19, 18, 2447 19, 17, 23, 19, 23, 21, 2448 21, 23, 22, 21, 22, 20, 2449 22, 16, 18, 22, 18, 20, 2450 2451 // corners 2452 0, 1, 5, 0, 5, 4, 2453 2, 3, 7, 2, 7, 6, 2454 8, 9, 13, 8, 13, 12, 2455 10, 11, 15, 10, 15, 14, 2456 2457 // edges 2458 1, 2, 6, 1, 6, 5, 2459 4, 5, 9, 4, 9, 8, 2460 6, 7, 11, 6, 11, 10, 2461 9, 10, 14, 9, 14, 13, 2462 2463 // center 2464 // we place this at the end so that we can ignore these indices when not rendering as filled 2465 5, 6, 10, 5, 10, 9, 2466 // clang-format on 2467}; 2468 2469// fill and standard stroke indices skip the overstroke "ring" 2470static const uint16_t* gStandardRRectIndices = gOverstrokeRRectIndices + 6 * 4; 2471 2472// overstroke count is arraysize minus the center indices 2473static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gOverstrokeRRectIndices) - 6; 2474// fill count skips overstroke indices and includes center 2475static const int kIndicesPerFillRRect = kIndicesPerOverstrokeRRect - 6 * 4 + 6; 2476// stroke count is fill count minus center indices 2477static const int kIndicesPerStrokeRRect = kIndicesPerFillRRect - 6; 2478static const int kVertsPerStandardRRect = 16; 2479static const int kVertsPerOverstrokeRRect = 24; 2480 2481enum RRectType { 2482 kFill_RRectType, 2483 kStroke_RRectType, 2484 kOverstroke_RRectType, 2485}; 2486 2487static int rrect_type_to_vert_count(RRectType type) { 2488 switch (type) { 2489 case kFill_RRectType: 2490 case kStroke_RRectType: 2491 return kVertsPerStandardRRect; 2492 case kOverstroke_RRectType: 2493 return kVertsPerOverstrokeRRect; 2494 } 2495 SK_ABORT("Invalid type"); 2496} 2497 2498static int rrect_type_to_index_count(RRectType type) { 2499 switch (type) { 2500 case kFill_RRectType: 2501 return kIndicesPerFillRRect; 2502 case kStroke_RRectType: 2503 return kIndicesPerStrokeRRect; 2504 case kOverstroke_RRectType: 2505 return kIndicesPerOverstrokeRRect; 2506 } 2507 SK_ABORT("Invalid type"); 2508} 2509 2510static const uint16_t* rrect_type_to_indices(RRectType type) { 2511 switch (type) { 2512 case kFill_RRectType: 2513 case kStroke_RRectType: 2514 return gStandardRRectIndices; 2515 case kOverstroke_RRectType: 2516 return gOverstrokeRRectIndices; 2517 } 2518 SK_ABORT("Invalid type"); 2519} 2520 2521/////////////////////////////////////////////////////////////////////////////////////////////////// 2522 2523// For distance computations in the interior of filled rrects we: 2524// 2525// add a interior degenerate (point or line) rect 2526// each vertex of that rect gets -outerRad as its radius 2527// this makes the computation of the distance to the outer edge be negative 2528// negative values are caught and then handled differently in the GP's onEmitCode 2529// each vertex is also given the normalized x & y distance from the interior rect's edge 2530// the GP takes the min of those depths +1 to get the normalized distance to the outer edge 2531 2532class CircularRRectOp : public GrMeshDrawOp { 2533private: 2534 using Helper = GrSimpleMeshDrawOpHelper; 2535 2536public: 2537 DEFINE_OP_CLASS_ID 2538 2539 // A devStrokeWidth <= 0 indicates a fill only. If devStrokeWidth > 0 then strokeOnly indicates 2540 // whether the rrect is only stroked or stroked and filled. 2541 static GrOp::Owner Make(GrRecordingContext* context, 2542 GrPaint&& paint, 2543 const SkMatrix& viewMatrix, 2544 const SkRect& devRect, 2545 float devRadius, 2546 float devStrokeWidth, 2547 bool strokeOnly) { 2548 return Helper::FactoryHelper<CircularRRectOp>(context, std::move(paint), viewMatrix, 2549 devRect, devRadius, 2550 devStrokeWidth, strokeOnly); 2551 } 2552 CircularRRectOp(GrProcessorSet* processorSet, const SkPMColor4f& color, 2553 const SkMatrix& viewMatrix, const SkRect& devRect, float devRadius, 2554 float devStrokeWidth, bool strokeOnly) 2555 : INHERITED(ClassID()) 2556 , fViewMatrixIfUsingLocalCoords(viewMatrix) 2557 , fHelper(processorSet, GrAAType::kCoverage) { 2558 SkRect bounds = devRect; 2559 SkASSERT(!(devStrokeWidth <= 0 && strokeOnly)); 2560 SkScalar innerRadius = 0.0f; 2561 SkScalar outerRadius = devRadius; 2562 SkScalar halfWidth = 0; 2563 RRectType type = kFill_RRectType; 2564 if (devStrokeWidth > 0) { 2565 if (SkScalarNearlyZero(devStrokeWidth)) { 2566 halfWidth = SK_ScalarHalf; 2567 } else { 2568 halfWidth = SkScalarHalf(devStrokeWidth); 2569 } 2570 2571 if (strokeOnly) { 2572 // Outset stroke by 1/4 pixel 2573 devStrokeWidth += 0.25f; 2574 // If stroke is greater than width or height, this is still a fill 2575 // Otherwise we compute stroke params 2576 if (devStrokeWidth <= devRect.width() && devStrokeWidth <= devRect.height()) { 2577 innerRadius = devRadius - halfWidth; 2578 type = (innerRadius >= 0) ? kStroke_RRectType : kOverstroke_RRectType; 2579 } 2580 } 2581 outerRadius += halfWidth; 2582 bounds.outset(halfWidth, halfWidth); 2583 } 2584 2585 // The radii are outset for two reasons. First, it allows the shader to simply perform 2586 // simpler computation because the computed alpha is zero, rather than 50%, at the radius. 2587 // Second, the outer radius is used to compute the verts of the bounding box that is 2588 // rendered and the outset ensures the box will cover all partially covered by the rrect 2589 // corners. 2590 outerRadius += SK_ScalarHalf; 2591 innerRadius -= SK_ScalarHalf; 2592 2593 this->setBounds(bounds, HasAABloat::kYes, IsHairline::kNo); 2594 2595 // Expand the rect for aa to generate correct vertices. 2596 bounds.outset(SK_ScalarHalf, SK_ScalarHalf); 2597 2598 fRRects.emplace_back(RRect{color, innerRadius, outerRadius, bounds, type}); 2599 fVertCount = rrect_type_to_vert_count(type); 2600 fIndexCount = rrect_type_to_index_count(type); 2601 fAllFill = (kFill_RRectType == type); 2602 } 2603 2604 const char* name() const override { return "CircularRRectOp"; } 2605 2606 void visitProxies(const GrVisitProxyFunc& func) const override { 2607 if (fProgramInfo) { 2608 fProgramInfo->visitFPProxies(func); 2609 } else { 2610 fHelper.visitProxies(func); 2611 } 2612 } 2613 2614 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip, 2615 GrClampType clampType) override { 2616 SkPMColor4f* color = &fRRects.front().fColor; 2617 return fHelper.finalizeProcessors(caps, clip, clampType, 2618 GrProcessorAnalysisCoverage::kSingleChannel, color, 2619 &fWideColor); 2620 } 2621 2622 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 2623 2624private: 2625 static void FillInOverstrokeVerts(VertexWriter& verts, const SkRect& bounds, SkScalar smInset, 2626 SkScalar bigInset, SkScalar xOffset, SkScalar outerRadius, 2627 SkScalar innerRadius, const GrVertexColor& color) { 2628 SkASSERT(smInset < bigInset); 2629 2630 // TL 2631 verts << (bounds.fLeft + smInset) << (bounds.fTop + smInset) 2632 << color 2633 << xOffset << 0.0f 2634 << outerRadius << innerRadius; 2635 2636 // TR 2637 verts << (bounds.fRight - smInset) << (bounds.fTop + smInset) 2638 << color 2639 << xOffset << 0.0f 2640 << outerRadius << innerRadius; 2641 2642 verts << (bounds.fLeft + bigInset) << (bounds.fTop + bigInset) 2643 << color 2644 << 0.0f << 0.0f 2645 << outerRadius << innerRadius; 2646 2647 verts << (bounds.fRight - bigInset) << (bounds.fTop + bigInset) 2648 << color 2649 << 0.0f << 0.0f 2650 << outerRadius << innerRadius; 2651 2652 verts << (bounds.fLeft + bigInset) << (bounds.fBottom - bigInset) 2653 << color 2654 << 0.0f << 0.0f 2655 << outerRadius << innerRadius; 2656 2657 verts << (bounds.fRight - bigInset) << (bounds.fBottom - bigInset) 2658 << color 2659 << 0.0f << 0.0f 2660 << outerRadius << innerRadius; 2661 2662 // BL 2663 verts << (bounds.fLeft + smInset) << (bounds.fBottom - smInset) 2664 << color 2665 << xOffset << 0.0f 2666 << outerRadius << innerRadius; 2667 2668 // BR 2669 verts << (bounds.fRight - smInset) << (bounds.fBottom - smInset) 2670 << color 2671 << xOffset << 0.0f 2672 << outerRadius << innerRadius; 2673 } 2674 2675 GrProgramInfo* programInfo() override { return fProgramInfo; } 2676 2677 void onCreateProgramInfo(const GrCaps* caps, 2678 SkArenaAlloc* arena, 2679 const GrSurfaceProxyView& writeView, 2680 bool usesMSAASurface, 2681 GrAppliedClip&& appliedClip, 2682 const GrDstProxyView& dstProxyView, 2683 GrXferBarrierFlags renderPassXferBarriers, 2684 GrLoadOp colorLoadOp) override { 2685 SkASSERT(!usesMSAASurface); 2686 2687 // Invert the view matrix as a local matrix (if any other processors require coords). 2688 SkMatrix localMatrix; 2689 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { 2690 return; 2691 } 2692 2693 GrGeometryProcessor* gp = CircleGeometryProcessor::Make(arena, !fAllFill, 2694 false, false, false, false, 2695 fWideColor, localMatrix); 2696 2697 fProgramInfo = fHelper.createProgramInfo(caps, arena, writeView, usesMSAASurface, 2698 std::move(appliedClip), dstProxyView, gp, 2699 GrPrimitiveType::kTriangles, 2700 renderPassXferBarriers, colorLoadOp); 2701 } 2702 2703 void onPrepareDraws(GrMeshDrawTarget* target) override { 2704 if (!fProgramInfo) { 2705 this->createProgramInfo(target); 2706 if (!fProgramInfo) { 2707 return; 2708 } 2709 } 2710 2711 sk_sp<const GrBuffer> vertexBuffer; 2712 int firstVertex; 2713 2714 VertexWriter verts{target->makeVertexSpace(fProgramInfo->geomProc().vertexStride(), 2715 fVertCount, &vertexBuffer, &firstVertex)}; 2716 if (!verts) { 2717 SkDebugf("Could not allocate vertices\n"); 2718 return; 2719 } 2720 2721 sk_sp<const GrBuffer> indexBuffer; 2722 int firstIndex = 0; 2723 uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex); 2724 if (!indices) { 2725 SkDebugf("Could not allocate indices\n"); 2726 return; 2727 } 2728 2729 int currStartVertex = 0; 2730 for (const auto& rrect : fRRects) { 2731 GrVertexColor color(rrect.fColor, fWideColor); 2732 SkScalar outerRadius = rrect.fOuterRadius; 2733 const SkRect& bounds = rrect.fDevBounds; 2734 2735 SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + outerRadius, 2736 bounds.fBottom - outerRadius, bounds.fBottom}; 2737 2738 SkScalar yOuterRadii[4] = {-1, 0, 0, 1}; 2739 // The inner radius in the vertex data must be specified in normalized space. 2740 // For fills, specifying -1/outerRadius guarantees an alpha of 1.0 at the inner radius. 2741 SkScalar innerRadius = rrect.fType != kFill_RRectType 2742 ? rrect.fInnerRadius / rrect.fOuterRadius 2743 : -1.0f / rrect.fOuterRadius; 2744 for (int i = 0; i < 4; ++i) { 2745 verts << bounds.fLeft << yCoords[i] 2746 << color 2747 << -1.0f << yOuterRadii[i] 2748 << outerRadius << innerRadius; 2749 2750 verts << (bounds.fLeft + outerRadius) << yCoords[i] 2751 << color 2752 << 0.0f << yOuterRadii[i] 2753 << outerRadius << innerRadius; 2754 2755 verts << (bounds.fRight - outerRadius) << yCoords[i] 2756 << color 2757 << 0.0f << yOuterRadii[i] 2758 << outerRadius << innerRadius; 2759 2760 verts << bounds.fRight << yCoords[i] 2761 << color 2762 << 1.0f << yOuterRadii[i] 2763 << outerRadius << innerRadius; 2764 } 2765 // Add the additional vertices for overstroked rrects. 2766 // Effectively this is an additional stroked rrect, with its 2767 // outer radius = outerRadius - innerRadius, and inner radius = 0. 2768 // This will give us correct AA in the center and the correct 2769 // distance to the outer edge. 2770 // 2771 // Also, the outer offset is a constant vector pointing to the right, which 2772 // guarantees that the distance value along the outer rectangle is constant. 2773 if (kOverstroke_RRectType == rrect.fType) { 2774 SkASSERT(rrect.fInnerRadius <= 0.0f); 2775 2776 SkScalar overstrokeOuterRadius = outerRadius - rrect.fInnerRadius; 2777 // this is the normalized distance from the outer rectangle of this 2778 // geometry to the outer edge 2779 SkScalar maxOffset = -rrect.fInnerRadius / overstrokeOuterRadius; 2780 2781 FillInOverstrokeVerts(verts, bounds, outerRadius, overstrokeOuterRadius, maxOffset, 2782 overstrokeOuterRadius, 0.0f, color); 2783 } 2784 2785 const uint16_t* primIndices = rrect_type_to_indices(rrect.fType); 2786 const int primIndexCount = rrect_type_to_index_count(rrect.fType); 2787 for (int i = 0; i < primIndexCount; ++i) { 2788 *indices++ = primIndices[i] + currStartVertex; 2789 } 2790 2791 currStartVertex += rrect_type_to_vert_count(rrect.fType); 2792 } 2793 2794 fMesh = target->allocMesh(); 2795 fMesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1, 2796 GrPrimitiveRestart::kNo, std::move(vertexBuffer), firstVertex); 2797 } 2798 2799 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { 2800 if (!fProgramInfo || !fMesh) { 2801 return; 2802 } 2803 2804 flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds); 2805 flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline()); 2806 flushState->drawMesh(*fMesh); 2807 } 2808 2809 CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override { 2810 CircularRRectOp* that = t->cast<CircularRRectOp>(); 2811 2812 // can only represent 65535 unique vertices with 16-bit indices 2813 if (fVertCount + that->fVertCount > 65536) { 2814 return CombineResult::kCannotCombine; 2815 } 2816 2817 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 2818 return CombineResult::kCannotCombine; 2819 } 2820 2821 if (fHelper.usesLocalCoords() && 2822 !SkMatrixPriv::CheapEqual(fViewMatrixIfUsingLocalCoords, 2823 that->fViewMatrixIfUsingLocalCoords)) { 2824 return CombineResult::kCannotCombine; 2825 } 2826 2827 fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin()); 2828 fVertCount += that->fVertCount; 2829 fIndexCount += that->fIndexCount; 2830 fAllFill = fAllFill && that->fAllFill; 2831 fWideColor = fWideColor || that->fWideColor; 2832 return CombineResult::kMerged; 2833 } 2834 2835#if GR_TEST_UTILS 2836 SkString onDumpInfo() const override { 2837 SkString string; 2838 for (int i = 0; i < fRRects.count(); ++i) { 2839 string.appendf( 2840 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]," 2841 "InnerRad: %.2f, OuterRad: %.2f\n", 2842 fRRects[i].fColor.toBytes_RGBA(), fRRects[i].fDevBounds.fLeft, 2843 fRRects[i].fDevBounds.fTop, fRRects[i].fDevBounds.fRight, 2844 fRRects[i].fDevBounds.fBottom, fRRects[i].fInnerRadius, 2845 fRRects[i].fOuterRadius); 2846 } 2847 string += fHelper.dumpInfo(); 2848 return string; 2849 } 2850#endif 2851 2852 struct RRect { 2853 SkPMColor4f fColor; 2854 SkScalar fInnerRadius; 2855 SkScalar fOuterRadius; 2856 SkRect fDevBounds; 2857 RRectType fType; 2858 }; 2859 2860 SkMatrix fViewMatrixIfUsingLocalCoords; 2861 Helper fHelper; 2862 int fVertCount; 2863 int fIndexCount; 2864 bool fAllFill; 2865 bool fWideColor; 2866 SkSTArray<1, RRect, true> fRRects; 2867 2868 GrSimpleMesh* fMesh = nullptr; 2869 GrProgramInfo* fProgramInfo = nullptr; 2870 2871 using INHERITED = GrMeshDrawOp; 2872}; 2873 2874static const int kNumRRectsInIndexBuffer = 256; 2875 2876GR_DECLARE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey); 2877GR_DECLARE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey); 2878static sk_sp<const GrBuffer> get_rrect_index_buffer(RRectType type, 2879 GrResourceProvider* resourceProvider) { 2880 GR_DEFINE_STATIC_UNIQUE_KEY(gStrokeRRectOnlyIndexBufferKey); 2881 GR_DEFINE_STATIC_UNIQUE_KEY(gRRectOnlyIndexBufferKey); 2882 switch (type) { 2883 case kFill_RRectType: 2884 return resourceProvider->findOrCreatePatternedIndexBuffer( 2885 gStandardRRectIndices, kIndicesPerFillRRect, kNumRRectsInIndexBuffer, 2886 kVertsPerStandardRRect, gRRectOnlyIndexBufferKey); 2887 case kStroke_RRectType: 2888 return resourceProvider->findOrCreatePatternedIndexBuffer( 2889 gStandardRRectIndices, kIndicesPerStrokeRRect, kNumRRectsInIndexBuffer, 2890 kVertsPerStandardRRect, gStrokeRRectOnlyIndexBufferKey); 2891 default: 2892 SkASSERT(false); 2893 return nullptr; 2894 } 2895} 2896 2897class EllipticalRRectOp : public GrMeshDrawOp { 2898private: 2899 using Helper = GrSimpleMeshDrawOpHelper; 2900 2901public: 2902 DEFINE_OP_CLASS_ID 2903 2904 // If devStrokeWidths values are <= 0 indicates then fill only. Otherwise, strokeOnly indicates 2905 // whether the rrect is only stroked or stroked and filled. 2906 static GrOp::Owner Make(GrRecordingContext* context, 2907 GrPaint&& paint, 2908 const SkMatrix& viewMatrix, 2909 const SkRect& devRect, 2910 float devXRadius, 2911 float devYRadius, 2912 SkVector devStrokeWidths, 2913 bool strokeOnly) { 2914 SkASSERT(devXRadius >= 0.5 || strokeOnly); 2915 SkASSERT(devYRadius >= 0.5 || strokeOnly); 2916 SkASSERT((devStrokeWidths.fX > 0) == (devStrokeWidths.fY > 0)); 2917 SkASSERT(!(strokeOnly && devStrokeWidths.fX <= 0)); 2918 if (devStrokeWidths.fX > 0) { 2919 if (SkScalarNearlyZero(devStrokeWidths.length())) { 2920 devStrokeWidths.set(SK_ScalarHalf, SK_ScalarHalf); 2921 } else { 2922 devStrokeWidths.scale(SK_ScalarHalf); 2923 } 2924 2925 // we only handle thick strokes for near-circular ellipses 2926 if (devStrokeWidths.length() > SK_ScalarHalf && 2927 (SK_ScalarHalf * devXRadius > devYRadius || 2928 SK_ScalarHalf * devYRadius > devXRadius)) { 2929 return nullptr; 2930 } 2931 2932 // we don't handle it if curvature of the stroke is less than curvature of the ellipse 2933 if (devStrokeWidths.fX * (devYRadius * devYRadius) < 2934 (devStrokeWidths.fY * devStrokeWidths.fY) * devXRadius) { 2935 return nullptr; 2936 } 2937 if (devStrokeWidths.fY * (devXRadius * devXRadius) < 2938 (devStrokeWidths.fX * devStrokeWidths.fX) * devYRadius) { 2939 return nullptr; 2940 } 2941 } 2942 return Helper::FactoryHelper<EllipticalRRectOp>(context, std::move(paint), 2943 viewMatrix, devRect, 2944 devXRadius, devYRadius, devStrokeWidths, 2945 strokeOnly); 2946 } 2947 2948 EllipticalRRectOp(GrProcessorSet* processorSet, const SkPMColor4f& color, 2949 const SkMatrix& viewMatrix, const SkRect& devRect, float devXRadius, 2950 float devYRadius, SkVector devStrokeHalfWidths, bool strokeOnly) 2951 : INHERITED(ClassID()) 2952 , fHelper(processorSet, GrAAType::kCoverage) 2953 , fUseScale(false) { 2954 SkScalar innerXRadius = 0.0f; 2955 SkScalar innerYRadius = 0.0f; 2956 SkRect bounds = devRect; 2957 bool stroked = false; 2958 if (devStrokeHalfWidths.fX > 0) { 2959 // this is legit only if scale & translation (which should be the case at the moment) 2960 if (strokeOnly) { 2961 innerXRadius = devXRadius - devStrokeHalfWidths.fX; 2962 innerYRadius = devYRadius - devStrokeHalfWidths.fY; 2963 stroked = (innerXRadius >= 0 && innerYRadius >= 0); 2964 } 2965 2966 devXRadius += devStrokeHalfWidths.fX; 2967 devYRadius += devStrokeHalfWidths.fY; 2968 bounds.outset(devStrokeHalfWidths.fX, devStrokeHalfWidths.fY); 2969 } 2970 2971 fStroked = stroked; 2972 fViewMatrixIfUsingLocalCoords = viewMatrix; 2973 this->setBounds(bounds, HasAABloat::kYes, IsHairline::kNo); 2974 fRRects.emplace_back( 2975 RRect{color, devXRadius, devYRadius, innerXRadius, innerYRadius, bounds}); 2976 } 2977 2978 const char* name() const override { return "EllipticalRRectOp"; } 2979 2980 void visitProxies(const GrVisitProxyFunc& func) const override { 2981 if (fProgramInfo) { 2982 fProgramInfo->visitFPProxies(func); 2983 } else { 2984 fHelper.visitProxies(func); 2985 } 2986 } 2987 2988 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip, 2989 GrClampType clampType) override { 2990 fUseScale = !caps.shaderCaps()->floatIs32Bits(); 2991 SkPMColor4f* color = &fRRects.front().fColor; 2992 return fHelper.finalizeProcessors(caps, clip, clampType, 2993 GrProcessorAnalysisCoverage::kSingleChannel, color, 2994 &fWideColor); 2995 } 2996 2997 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 2998 2999private: 3000 GrProgramInfo* programInfo() override { return fProgramInfo; } 3001 3002 void onCreateProgramInfo(const GrCaps* caps, 3003 SkArenaAlloc* arena, 3004 const GrSurfaceProxyView& writeView, 3005 bool usesMSAASurface, 3006 GrAppliedClip&& appliedClip, 3007 const GrDstProxyView& dstProxyView, 3008 GrXferBarrierFlags renderPassXferBarriers, 3009 GrLoadOp colorLoadOp) override { 3010 SkMatrix localMatrix; 3011 if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { 3012 return; 3013 } 3014 3015 GrGeometryProcessor* gp = EllipseGeometryProcessor::Make(arena, fStroked, fWideColor, 3016 fUseScale, localMatrix); 3017 3018 fProgramInfo = fHelper.createProgramInfo(caps, arena, writeView, usesMSAASurface, 3019 std::move(appliedClip), dstProxyView, gp, 3020 GrPrimitiveType::kTriangles, 3021 renderPassXferBarriers, colorLoadOp); 3022 } 3023 3024 void onPrepareDraws(GrMeshDrawTarget* target) override { 3025 if (!fProgramInfo) { 3026 this->createProgramInfo(target); 3027 if (!fProgramInfo) { 3028 return; 3029 } 3030 } 3031 3032 // drop out the middle quad if we're stroked 3033 int indicesPerInstance = fStroked ? kIndicesPerStrokeRRect : kIndicesPerFillRRect; 3034 sk_sp<const GrBuffer> indexBuffer = get_rrect_index_buffer( 3035 fStroked ? kStroke_RRectType : kFill_RRectType, target->resourceProvider()); 3036 3037 if (!indexBuffer) { 3038 SkDebugf("Could not allocate indices\n"); 3039 return; 3040 } 3041 PatternHelper helper(target, GrPrimitiveType::kTriangles, 3042 fProgramInfo->geomProc().vertexStride(), 3043 std::move(indexBuffer), kVertsPerStandardRRect, indicesPerInstance, 3044 fRRects.count(), kNumRRectsInIndexBuffer); 3045 VertexWriter verts{helper.vertices()}; 3046 if (!verts) { 3047 SkDebugf("Could not allocate vertices\n"); 3048 return; 3049 } 3050 3051 for (const auto& rrect : fRRects) { 3052 GrVertexColor color(rrect.fColor, fWideColor); 3053 // Compute the reciprocals of the radii here to save time in the shader 3054 float reciprocalRadii[4] = { 3055 SkScalarInvert(rrect.fXRadius), 3056 SkScalarInvert(rrect.fYRadius), 3057 SkScalarInvert(rrect.fInnerXRadius), 3058 SkScalarInvert(rrect.fInnerYRadius) 3059 }; 3060 3061 // If the stroke width is exactly double the radius, the inner radii will be zero. 3062 // Pin to a large value, to avoid infinities in the shader. crbug.com/1139750 3063 reciprocalRadii[2] = std::min(reciprocalRadii[2], 1e6f); 3064 reciprocalRadii[3] = std::min(reciprocalRadii[3], 1e6f); 3065 3066 // On MSAA, bloat enough to guarantee any pixel that might be touched by the rrect has 3067 // full sample coverage. 3068 float aaBloat = target->usesMSAASurface() ? SK_ScalarSqrt2 : .5f; 3069 3070 // Extend out the radii to antialias. 3071 SkScalar xOuterRadius = rrect.fXRadius + aaBloat; 3072 SkScalar yOuterRadius = rrect.fYRadius + aaBloat; 3073 3074 SkScalar xMaxOffset = xOuterRadius; 3075 SkScalar yMaxOffset = yOuterRadius; 3076 if (!fStroked) { 3077 // For filled rrects we map a unit circle in the vertex attributes rather than 3078 // computing an ellipse and modifying that distance, so we normalize to 1. 3079 xMaxOffset /= rrect.fXRadius; 3080 yMaxOffset /= rrect.fYRadius; 3081 } 3082 3083 const SkRect& bounds = rrect.fDevBounds.makeOutset(aaBloat, aaBloat); 3084 3085 SkScalar yCoords[4] = {bounds.fTop, bounds.fTop + yOuterRadius, 3086 bounds.fBottom - yOuterRadius, bounds.fBottom}; 3087 SkScalar yOuterOffsets[4] = {yMaxOffset, 3088 SK_ScalarNearlyZero, // we're using inversesqrt() in 3089 // shader, so can't be exactly 0 3090 SK_ScalarNearlyZero, yMaxOffset}; 3091 3092 auto maybeScale = VertexWriter::If(fUseScale, std::max(rrect.fXRadius, rrect.fYRadius)); 3093 for (int i = 0; i < 4; ++i) { 3094 verts << bounds.fLeft << yCoords[i] 3095 << color 3096 << xMaxOffset << yOuterOffsets[i] 3097 << maybeScale 3098 << reciprocalRadii; 3099 3100 verts << (bounds.fLeft + xOuterRadius) << yCoords[i] 3101 << color 3102 << SK_ScalarNearlyZero << yOuterOffsets[i] 3103 << maybeScale 3104 << reciprocalRadii; 3105 3106 verts << (bounds.fRight - xOuterRadius) << yCoords[i] 3107 << color 3108 << SK_ScalarNearlyZero << yOuterOffsets[i] 3109 << maybeScale 3110 << reciprocalRadii; 3111 3112 verts << bounds.fRight << yCoords[i] 3113 << color 3114 << xMaxOffset << yOuterOffsets[i] 3115 << maybeScale 3116 << reciprocalRadii; 3117 } 3118 } 3119 fMesh = helper.mesh(); 3120 } 3121 3122 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { 3123 if (!fProgramInfo || !fMesh) { 3124 return; 3125 } 3126 3127 flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds); 3128 flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline()); 3129 flushState->drawMesh(*fMesh); 3130 } 3131 3132 CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override { 3133 EllipticalRRectOp* that = t->cast<EllipticalRRectOp>(); 3134 3135 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 3136 return CombineResult::kCannotCombine; 3137 } 3138 3139 if (fStroked != that->fStroked) { 3140 return CombineResult::kCannotCombine; 3141 } 3142 3143 if (fHelper.usesLocalCoords() && 3144 !SkMatrixPriv::CheapEqual(fViewMatrixIfUsingLocalCoords, 3145 that->fViewMatrixIfUsingLocalCoords)) { 3146 return CombineResult::kCannotCombine; 3147 } 3148 3149 fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin()); 3150 fWideColor = fWideColor || that->fWideColor; 3151 return CombineResult::kMerged; 3152 } 3153 3154#if GR_TEST_UTILS 3155 SkString onDumpInfo() const override { 3156 SkString string = SkStringPrintf("Stroked: %d\n", fStroked); 3157 for (const auto& geo : fRRects) { 3158 string.appendf( 3159 "Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], " 3160 "XRad: %.2f, YRad: %.2f, InnerXRad: %.2f, InnerYRad: %.2f\n", 3161 geo.fColor.toBytes_RGBA(), geo.fDevBounds.fLeft, geo.fDevBounds.fTop, 3162 geo.fDevBounds.fRight, geo.fDevBounds.fBottom, geo.fXRadius, geo.fYRadius, 3163 geo.fInnerXRadius, geo.fInnerYRadius); 3164 } 3165 string += fHelper.dumpInfo(); 3166 return string; 3167 } 3168#endif 3169 3170 struct RRect { 3171 SkPMColor4f fColor; 3172 SkScalar fXRadius; 3173 SkScalar fYRadius; 3174 SkScalar fInnerXRadius; 3175 SkScalar fInnerYRadius; 3176 SkRect fDevBounds; 3177 }; 3178 3179 SkMatrix fViewMatrixIfUsingLocalCoords; 3180 Helper fHelper; 3181 bool fStroked; 3182 bool fWideColor; 3183 bool fUseScale; 3184 SkSTArray<1, RRect, true> fRRects; 3185 3186 GrSimpleMesh* fMesh = nullptr; 3187 GrProgramInfo* fProgramInfo = nullptr; 3188 3189 using INHERITED = GrMeshDrawOp; 3190}; 3191 3192GrOp::Owner GrOvalOpFactory::MakeCircularRRectOp(GrRecordingContext* context, 3193 GrPaint&& paint, 3194 const SkMatrix& viewMatrix, 3195 const SkRRect& rrect, 3196 const SkStrokeRec& stroke, 3197 const GrShaderCaps* shaderCaps) { 3198 SkASSERT(viewMatrix.rectStaysRect()); 3199 SkASSERT(viewMatrix.isSimilarity()); 3200 SkASSERT(rrect.isSimple()); 3201 SkASSERT(!rrect.isOval()); 3202 SkASSERT(SkRRectPriv::GetSimpleRadii(rrect).fX == SkRRectPriv::GetSimpleRadii(rrect).fY); 3203 3204 // RRect ops only handle simple, but not too simple, rrects. 3205 // Do any matrix crunching before we reset the draw state for device coords. 3206 const SkRect& rrectBounds = rrect.getBounds(); 3207 SkRect bounds; 3208 viewMatrix.mapRect(&bounds, rrectBounds); 3209 3210 SkScalar radius = SkRRectPriv::GetSimpleRadii(rrect).fX; 3211 SkScalar scaledRadius = SkScalarAbs(radius * (viewMatrix[SkMatrix::kMScaleX] + 3212 viewMatrix[SkMatrix::kMSkewY])); 3213 3214 // Do mapping of stroke. Use -1 to indicate fill-only draws. 3215 SkScalar scaledStroke = -1; 3216 SkScalar strokeWidth = stroke.getWidth(); 3217 SkStrokeRec::Style style = stroke.getStyle(); 3218 3219 bool isStrokeOnly = 3220 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style; 3221 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style; 3222 3223 if (hasStroke) { 3224 if (SkStrokeRec::kHairline_Style == style) { 3225 scaledStroke = SK_Scalar1; 3226 } else { 3227 scaledStroke = SkScalarAbs(strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + 3228 viewMatrix[SkMatrix::kMSkewY])); 3229 } 3230 } 3231 3232 // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on 3233 // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine- 3234 // patch will have fractional coverage. This only matters when the interior is actually filled. 3235 // We could consider falling back to rect rendering here, since a tiny radius is 3236 // indistinguishable from a square corner. 3237 if (!isStrokeOnly && SK_ScalarHalf > scaledRadius) { 3238 return nullptr; 3239 } 3240 3241 return CircularRRectOp::Make(context, std::move(paint), viewMatrix, bounds, scaledRadius, 3242 scaledStroke, isStrokeOnly); 3243} 3244 3245GrOp::Owner make_rrect_op(GrRecordingContext* context, 3246 GrPaint&& paint, 3247 const SkMatrix& viewMatrix, 3248 const SkRRect& rrect, 3249 const SkStrokeRec& stroke) { 3250 SkASSERT(viewMatrix.rectStaysRect()); 3251 SkASSERT(rrect.isSimple()); 3252 SkASSERT(!rrect.isOval()); 3253 3254 // RRect ops only handle simple, but not too simple, rrects. 3255 // Do any matrix crunching before we reset the draw state for device coords. 3256 const SkRect& rrectBounds = rrect.getBounds(); 3257 SkRect bounds; 3258 viewMatrix.mapRect(&bounds, rrectBounds); 3259 3260 SkVector radii = SkRRectPriv::GetSimpleRadii(rrect); 3261 SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * radii.fX + 3262 viewMatrix[SkMatrix::kMSkewY] * radii.fY); 3263 SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX] * radii.fX + 3264 viewMatrix[SkMatrix::kMScaleY] * radii.fY); 3265 3266 SkStrokeRec::Style style = stroke.getStyle(); 3267 3268 // Do (potentially) anisotropic mapping of stroke. Use -1s to indicate fill-only draws. 3269 SkVector scaledStroke = {-1, -1}; 3270 SkScalar strokeWidth = stroke.getWidth(); 3271 3272 bool isStrokeOnly = 3273 SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style; 3274 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style; 3275 3276 if (hasStroke) { 3277 if (SkStrokeRec::kHairline_Style == style) { 3278 scaledStroke.set(1, 1); 3279 } else { 3280 scaledStroke.fX = SkScalarAbs( 3281 strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY])); 3282 scaledStroke.fY = SkScalarAbs( 3283 strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY])); 3284 } 3285 3286 // if half of strokewidth is greater than radius, we don't handle that right now 3287 if ((SK_ScalarHalf * scaledStroke.fX > xRadius || 3288 SK_ScalarHalf * scaledStroke.fY > yRadius)) { 3289 return nullptr; 3290 } 3291 } 3292 3293 // The matrix may have a rotation by an odd multiple of 90 degrees. 3294 if (viewMatrix.getScaleX() == 0) { 3295 std::swap(xRadius, yRadius); 3296 std::swap(scaledStroke.fX, scaledStroke.fY); 3297 } 3298 3299 // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on 3300 // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine- 3301 // patch will have fractional coverage. This only matters when the interior is actually filled. 3302 // We could consider falling back to rect rendering here, since a tiny radius is 3303 // indistinguishable from a square corner. 3304 if (!isStrokeOnly && (SK_ScalarHalf > xRadius || SK_ScalarHalf > yRadius)) { 3305 return nullptr; 3306 } 3307 3308 // if the corners are circles, use the circle renderer 3309 return EllipticalRRectOp::Make(context, std::move(paint), viewMatrix, bounds, 3310 xRadius, yRadius, scaledStroke, isStrokeOnly); 3311} 3312 3313GrOp::Owner GrOvalOpFactory::MakeRRectOp(GrRecordingContext* context, 3314 GrPaint&& paint, 3315 const SkMatrix& viewMatrix, 3316 const SkRRect& rrect, 3317 const SkStrokeRec& stroke, 3318 const GrShaderCaps* shaderCaps) { 3319 if (rrect.isOval()) { 3320 return MakeOvalOp(context, std::move(paint), viewMatrix, rrect.getBounds(), 3321 GrStyle(stroke, nullptr), shaderCaps); 3322 } 3323 3324 if (!viewMatrix.rectStaysRect() || !rrect.isSimple()) { 3325 return nullptr; 3326 } 3327 3328 return make_rrect_op(context, std::move(paint), viewMatrix, rrect, stroke); 3329} 3330 3331/////////////////////////////////////////////////////////////////////////////// 3332 3333GrOp::Owner GrOvalOpFactory::MakeCircleOp(GrRecordingContext* context, 3334 GrPaint&& paint, 3335 const SkMatrix& viewMatrix, 3336 const SkRect& oval, 3337 const GrStyle& style, 3338 const GrShaderCaps* shaderCaps) { 3339 SkScalar width = oval.width(); 3340 SkASSERT(width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) && 3341 circle_stays_circle(viewMatrix)); 3342 3343 auto r = width / 2.f; 3344 SkPoint center = { oval.centerX(), oval.centerY() }; 3345 if (style.hasNonDashPathEffect()) { 3346 return nullptr; 3347 } else if (style.isDashed()) { 3348 if (style.strokeRec().getCap() != SkPaint::kButt_Cap || 3349 style.dashIntervalCnt() != 2 || style.strokeRec().getWidth() >= width) { 3350 return nullptr; 3351 } 3352 auto onInterval = style.dashIntervals()[0]; 3353 auto offInterval = style.dashIntervals()[1]; 3354 if (offInterval == 0) { 3355 GrStyle strokeStyle(style.strokeRec(), nullptr); 3356 return MakeOvalOp(context, std::move(paint), viewMatrix, oval, 3357 strokeStyle, shaderCaps); 3358 } else if (onInterval == 0) { 3359 // There is nothing to draw but we have no way to indicate that here. 3360 return nullptr; 3361 } 3362 auto angularOnInterval = onInterval / r; 3363 auto angularOffInterval = offInterval / r; 3364 auto phaseAngle = style.dashPhase() / r; 3365 // Currently this function doesn't accept ovals with different start angles, though 3366 // it could. 3367 static const SkScalar kStartAngle = 0.f; 3368 return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix, center, r, 3369 style.strokeRec().getWidth(), kStartAngle, 3370 angularOnInterval, angularOffInterval, phaseAngle); 3371 } 3372 return CircleOp::Make(context, std::move(paint), viewMatrix, center, r, style); 3373} 3374 3375GrOp::Owner GrOvalOpFactory::MakeOvalOp(GrRecordingContext* context, 3376 GrPaint&& paint, 3377 const SkMatrix& viewMatrix, 3378 const SkRect& oval, 3379 const GrStyle& style, 3380 const GrShaderCaps* shaderCaps) { 3381 if (style.pathEffect()) { 3382 return nullptr; 3383 } 3384 3385 // prefer the device space ellipse op for batchability 3386 if (viewMatrix.rectStaysRect()) { 3387 return EllipseOp::Make(context, std::move(paint), viewMatrix, oval, style.strokeRec()); 3388 } 3389 3390 // Otherwise, if we have shader derivative support, render as device-independent 3391 if (shaderCaps->shaderDerivativeSupport()) { 3392 SkScalar a = viewMatrix[SkMatrix::kMScaleX]; 3393 SkScalar b = viewMatrix[SkMatrix::kMSkewX]; 3394 SkScalar c = viewMatrix[SkMatrix::kMSkewY]; 3395 SkScalar d = viewMatrix[SkMatrix::kMScaleY]; 3396 // Check for near-degenerate matrix 3397 if (a*a + c*c > SK_ScalarNearlyZero && b*b + d*d > SK_ScalarNearlyZero) { 3398 return DIEllipseOp::Make(context, std::move(paint), viewMatrix, oval, 3399 style.strokeRec()); 3400 } 3401 } 3402 3403 return nullptr; 3404} 3405 3406/////////////////////////////////////////////////////////////////////////////// 3407 3408GrOp::Owner GrOvalOpFactory::MakeArcOp(GrRecordingContext* context, 3409 GrPaint&& paint, 3410 const SkMatrix& viewMatrix, 3411 const SkRect& oval, SkScalar startAngle, 3412 SkScalar sweepAngle, bool useCenter, 3413 const GrStyle& style, 3414 const GrShaderCaps* shaderCaps) { 3415 SkASSERT(!oval.isEmpty()); 3416 SkASSERT(sweepAngle); 3417 SkScalar width = oval.width(); 3418 if (SkScalarAbs(sweepAngle) >= 360.f) { 3419 return nullptr; 3420 } 3421 if (!SkScalarNearlyEqual(width, oval.height()) || !circle_stays_circle(viewMatrix)) { 3422 return nullptr; 3423 } 3424 SkPoint center = {oval.centerX(), oval.centerY()}; 3425 CircleOp::ArcParams arcParams = {SkDegreesToRadians(startAngle), SkDegreesToRadians(sweepAngle), 3426 useCenter}; 3427 return CircleOp::Make(context, std::move(paint), viewMatrix, 3428 center, width / 2.f, style, &arcParams); 3429} 3430 3431/////////////////////////////////////////////////////////////////////////////// 3432 3433#if GR_TEST_UTILS 3434 3435GR_DRAW_OP_TEST_DEFINE(CircleOp) { 3436 if (numSamples > 1) { 3437 return nullptr; 3438 } 3439 3440 do { 3441 SkScalar rotate = random->nextSScalar1() * 360.f; 3442 SkScalar translateX = random->nextSScalar1() * 1000.f; 3443 SkScalar translateY = random->nextSScalar1() * 1000.f; 3444 SkScalar scale; 3445 do { 3446 scale = random->nextSScalar1() * 100.f; 3447 } while (scale == 0); 3448 SkMatrix viewMatrix; 3449 viewMatrix.setRotate(rotate); 3450 viewMatrix.postTranslate(translateX, translateY); 3451 viewMatrix.postScale(scale, scale); 3452 SkRect circle = GrTest::TestSquare(random); 3453 SkPoint center = {circle.centerX(), circle.centerY()}; 3454 SkScalar radius = circle.width() / 2.f; 3455 SkStrokeRec stroke = GrTest::TestStrokeRec(random); 3456 CircleOp::ArcParams arcParamsTmp; 3457 const CircleOp::ArcParams* arcParams = nullptr; 3458 if (random->nextBool()) { 3459 arcParamsTmp.fStartAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2; 3460 arcParamsTmp.fSweepAngleRadians = random->nextSScalar1() * SK_ScalarPI * 2 - .01f; 3461 arcParamsTmp.fUseCenter = random->nextBool(); 3462 arcParams = &arcParamsTmp; 3463 } 3464 GrOp::Owner op = CircleOp::Make(context, std::move(paint), viewMatrix, 3465 center, radius, 3466 GrStyle(stroke, nullptr), arcParams); 3467 if (op) { 3468 return op; 3469 } 3470 assert_alive(paint); 3471 } while (true); 3472} 3473 3474GR_DRAW_OP_TEST_DEFINE(ButtCapDashedCircleOp) { 3475 if (numSamples > 1) { 3476 return nullptr; 3477 } 3478 3479 SkScalar rotate = random->nextSScalar1() * 360.f; 3480 SkScalar translateX = random->nextSScalar1() * 1000.f; 3481 SkScalar translateY = random->nextSScalar1() * 1000.f; 3482 SkScalar scale; 3483 do { 3484 scale = random->nextSScalar1() * 100.f; 3485 } while (scale == 0); 3486 SkMatrix viewMatrix; 3487 viewMatrix.setRotate(rotate); 3488 viewMatrix.postTranslate(translateX, translateY); 3489 viewMatrix.postScale(scale, scale); 3490 SkRect circle = GrTest::TestSquare(random); 3491 SkPoint center = {circle.centerX(), circle.centerY()}; 3492 SkScalar radius = circle.width() / 2.f; 3493 SkScalar strokeWidth = random->nextRangeScalar(0.001f * radius, 1.8f * radius); 3494 SkScalar onAngle = random->nextRangeScalar(0.01f, 1000.f); 3495 SkScalar offAngle = random->nextRangeScalar(0.01f, 1000.f); 3496 SkScalar startAngle = random->nextRangeScalar(-1000.f, 1000.f); 3497 SkScalar phase = random->nextRangeScalar(-1000.f, 1000.f); 3498 return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix, 3499 center, radius, strokeWidth, 3500 startAngle, onAngle, offAngle, phase); 3501} 3502 3503GR_DRAW_OP_TEST_DEFINE(EllipseOp) { 3504 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random); 3505 SkRect ellipse = GrTest::TestSquare(random); 3506 return EllipseOp::Make(context, std::move(paint), viewMatrix, ellipse, 3507 GrTest::TestStrokeRec(random)); 3508} 3509 3510GR_DRAW_OP_TEST_DEFINE(DIEllipseOp) { 3511 SkMatrix viewMatrix = GrTest::TestMatrix(random); 3512 SkRect ellipse = GrTest::TestSquare(random); 3513 return DIEllipseOp::Make(context, std::move(paint), viewMatrix, ellipse, 3514 GrTest::TestStrokeRec(random)); 3515} 3516 3517GR_DRAW_OP_TEST_DEFINE(CircularRRectOp) { 3518 do { 3519 SkScalar rotate = random->nextSScalar1() * 360.f; 3520 SkScalar translateX = random->nextSScalar1() * 1000.f; 3521 SkScalar translateY = random->nextSScalar1() * 1000.f; 3522 SkScalar scale; 3523 do { 3524 scale = random->nextSScalar1() * 100.f; 3525 } while (scale == 0); 3526 SkMatrix viewMatrix; 3527 viewMatrix.setRotate(rotate); 3528 viewMatrix.postTranslate(translateX, translateY); 3529 viewMatrix.postScale(scale, scale); 3530 SkRect rect = GrTest::TestRect(random); 3531 SkScalar radius = random->nextRangeF(0.1f, 10.f); 3532 SkRRect rrect = SkRRect::MakeRectXY(rect, radius, radius); 3533 if (rrect.isOval()) { 3534 continue; 3535 } 3536 GrOp::Owner op = 3537 GrOvalOpFactory::MakeCircularRRectOp(context, std::move(paint), viewMatrix, rrect, 3538 GrTest::TestStrokeRec(random), nullptr); 3539 if (op) { 3540 return op; 3541 } 3542 assert_alive(paint); 3543 } while (true); 3544} 3545 3546GR_DRAW_OP_TEST_DEFINE(RRectOp) { 3547 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random); 3548 const SkRRect& rrect = GrTest::TestRRectSimple(random); 3549 return make_rrect_op(context, std::move(paint), viewMatrix, rrect, 3550 GrTest::TestStrokeRec(random)); 3551} 3552 3553#endif 3554