1 /* 2 * Copyright 2018 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/GrFillRRectOp.h" 9 10 #include "include/private/GrRecordingContext.h" 11 #include "src/core/SkRRectPriv.h" 12 #include "src/gpu/GrCaps.h" 13 #include "src/gpu/GrMemoryPool.h" 14 #include "src/gpu/GrOpFlushState.h" 15 #include "src/gpu/GrOpsRenderPass.h" 16 #include "src/gpu/GrProgramInfo.h" 17 #include "src/gpu/GrRecordingContextPriv.h" 18 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" 19 #include "src/gpu/glsl/GrGLSLGeometryProcessor.h" 20 #include "src/gpu/glsl/GrGLSLVarying.h" 21 #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" 22 23 // Hardware derivatives are not always accurate enough for highly elliptical corners. This method 24 // checks to make sure the corners will still all look good if we use HW derivatives. 25 static bool can_use_hw_derivatives_with_coverage( 26 const GrShaderCaps&, const SkMatrix&, const SkRRect&); 27 Make(GrRecordingContext * ctx,GrAAType aaType,const SkMatrix & viewMatrix,const SkRRect & rrect,const GrCaps & caps,GrPaint && paint)28 std::unique_ptr<GrFillRRectOp> GrFillRRectOp::Make( 29 GrRecordingContext* ctx, GrAAType aaType, const SkMatrix& viewMatrix, const SkRRect& rrect, 30 const GrCaps& caps, GrPaint&& paint) { 31 if (!caps.instanceAttribSupport()) { 32 return nullptr; 33 } 34 35 Flags flags = Flags::kNone; 36 if (GrAAType::kCoverage == aaType) { 37 // TODO: Support perspective in a follow-on CL. This shouldn't be difficult, since we 38 // already use HW derivatives. The only trick will be adjusting the AA outset to account for 39 // perspective. (i.e., outset = 0.5 * z.) 40 if (viewMatrix.hasPerspective()) { 41 return nullptr; 42 } 43 if (can_use_hw_derivatives_with_coverage(*caps.shaderCaps(), viewMatrix, rrect)) { 44 // HW derivatives (more specifically, fwidth()) are consistently faster on all platforms 45 // in coverage mode. We use them as long as the approximation will be accurate enough. 46 flags |= Flags::kUseHWDerivatives; 47 } 48 } else { 49 if (GrAAType::kMSAA == aaType) { 50 if (!caps.sampleLocationsSupport() || !caps.shaderCaps()->sampleMaskSupport() || 51 caps.shaderCaps()->canOnlyUseSampleMaskWithStencil()) { 52 return nullptr; 53 } 54 } 55 if (viewMatrix.hasPerspective()) { 56 // HW derivatives are consistently slower on all platforms in sample mask mode. We 57 // therefore only use them when there is perspective, since then we can't interpolate 58 // the symbolic screen-space gradient. 59 flags |= Flags::kUseHWDerivatives | Flags::kHasPerspective; 60 } 61 } 62 63 // Produce a matrix that draws the round rect from normalized [-1, -1, +1, +1] space. 64 float l = rrect.rect().left(), r = rrect.rect().right(), 65 t = rrect.rect().top(), b = rrect.rect().bottom(); 66 SkMatrix m; 67 // Unmap the normalized rect [-1, -1, +1, +1] back to [l, t, r, b]. 68 m.setScaleTranslate((r - l)/2, (b - t)/2, (l + r)/2, (t + b)/2); 69 // Map to device space. 70 m.postConcat(viewMatrix); 71 72 SkRect devBounds; 73 if (!(flags & Flags::kHasPerspective)) { 74 // Since m is an affine matrix that maps the rect [-1, -1, +1, +1] into the shape's 75 // device-space quad, it's quite simple to find the bounding rectangle: 76 devBounds = SkRect::MakeXYWH(m.getTranslateX(), m.getTranslateY(), 0, 0); 77 devBounds.outset(SkScalarAbs(m.getScaleX()) + SkScalarAbs(m.getSkewX()), 78 SkScalarAbs(m.getSkewY()) + SkScalarAbs(m.getScaleY())); 79 } else { 80 viewMatrix.mapRect(&devBounds, rrect.rect()); 81 } 82 83 if (GrAAType::kMSAA == aaType && caps.preferTrianglesOverSampleMask()) { 84 // We are on a platform that prefers fine triangles instead of using the sample mask. See if 85 // the round rect is large enough that it will be faster for us to send it off to the 86 // default path renderer instead. The 200x200 threshold was arrived at using the 87 // "shapes_rrect" benchmark on an ARM Galaxy S9. 88 if (devBounds.height() * devBounds.width() > 200 * 200) { 89 return nullptr; 90 } 91 } 92 93 GrOpMemoryPool* pool = ctx->priv().opMemoryPool(); 94 return pool->allocate<GrFillRRectOp>(aaType, rrect, flags, m, std::move(paint), devBounds); 95 } 96 GrFillRRectOp(GrAAType aaType,const SkRRect & rrect,Flags flags,const SkMatrix & totalShapeMatrix,GrPaint && paint,const SkRect & devBounds)97 GrFillRRectOp::GrFillRRectOp(GrAAType aaType, const SkRRect& rrect, Flags flags, 98 const SkMatrix& totalShapeMatrix, GrPaint&& paint, 99 const SkRect& devBounds) 100 : GrDrawOp(ClassID()) 101 , fAAType(aaType) 102 , fOriginalColor(paint.getColor4f()) 103 , fLocalRect(rrect.rect()) 104 , fFlags(flags) 105 , fProcessors(std::move(paint)) { 106 SkASSERT((fFlags & Flags::kHasPerspective) == totalShapeMatrix.hasPerspective()); 107 this->setBounds(devBounds, GrOp::HasAABloat::kYes, GrOp::IsHairline::kNo); 108 109 // Write the matrix attribs. 110 const SkMatrix& m = totalShapeMatrix; 111 if (!(fFlags & Flags::kHasPerspective)) { 112 // Affine 2D transformation (float2x2 plus float2 translate). 113 SkASSERT(!m.hasPerspective()); 114 this->writeInstanceData(m.getScaleX(), m.getSkewX(), m.getSkewY(), m.getScaleY()); 115 this->writeInstanceData(m.getTranslateX(), m.getTranslateY()); 116 } else { 117 // Perspective float3x3 transformation matrix. 118 SkASSERT(m.hasPerspective()); 119 m.get9(this->appendInstanceData<float>(9)); 120 } 121 122 // Convert the radii to [-1, -1, +1, +1] space and write their attribs. 123 Sk4f radiiX, radiiY; 124 Sk4f::Load2(SkRRectPriv::GetRadiiArray(rrect), &radiiX, &radiiY); 125 (radiiX * (2/rrect.width())).store(this->appendInstanceData<float>(4)); 126 (radiiY * (2/rrect.height())).store(this->appendInstanceData<float>(4)); 127 128 // We will write the color and local rect attribs during finalize(). 129 } 130 finalize(const GrCaps & caps,const GrAppliedClip * clip,bool hasMixedSampledCoverage,GrClampType clampType)131 GrProcessorSet::Analysis GrFillRRectOp::finalize( 132 const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, 133 GrClampType clampType) { 134 SkASSERT(1 == fInstanceCount); 135 136 SkPMColor4f overrideColor; 137 const GrProcessorSet::Analysis& analysis = fProcessors.finalize( 138 fOriginalColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, 139 &GrUserStencilSettings::kUnused, hasMixedSampledCoverage, caps, clampType, 140 &overrideColor); 141 142 // Finish writing the instance attribs. 143 SkPMColor4f finalColor = analysis.inputColorIsOverridden() ? overrideColor : fOriginalColor; 144 if (!SkPMColor4fFitsInBytes(finalColor)) { 145 fFlags |= Flags::kWideColor; 146 this->writeInstanceData(finalColor); 147 } else { 148 this->writeInstanceData(finalColor.toBytes_RGBA()); 149 } 150 151 if (analysis.usesLocalCoords()) { 152 this->writeInstanceData(fLocalRect); 153 fFlags |= Flags::kHasLocalCoords; 154 } 155 fInstanceStride = fInstanceData.count(); 156 157 return analysis; 158 } 159 onCombineIfPossible(GrOp * op,GrRecordingContext::Arenas *,const GrCaps &)160 GrDrawOp::CombineResult GrFillRRectOp::onCombineIfPossible(GrOp* op, GrRecordingContext::Arenas*, 161 const GrCaps&) { 162 const auto& that = *op->cast<GrFillRRectOp>(); 163 if (fFlags != that.fFlags || fProcessors != that.fProcessors || 164 fInstanceData.count() > std::numeric_limits<int>::max() - that.fInstanceData.count()) { 165 return CombineResult::kCannotCombine; 166 } 167 168 fInstanceData.push_back_n(that.fInstanceData.count(), that.fInstanceData.begin()); 169 fInstanceCount += that.fInstanceCount; 170 SkASSERT(fInstanceStride == that.fInstanceStride); 171 return CombineResult::kMerged; 172 } 173 174 class GrFillRRectOp::Processor : public GrGeometryProcessor { 175 public: Make(SkArenaAlloc * arena,GrAAType aaType,Flags flags)176 static GrGeometryProcessor* Make(SkArenaAlloc* arena, GrAAType aaType, Flags flags) { 177 return arena->make<Processor>(aaType, flags); 178 } 179 name() const180 const char* name() const final { return "GrFillRRectOp::Processor"; } 181 getGLSLProcessorKey(const GrShaderCaps & caps,GrProcessorKeyBuilder * b) const182 void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const final { 183 b->add32(((uint32_t)fFlags << 16) | (uint32_t)fAAType); 184 } 185 186 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final; 187 188 private: 189 friend class ::SkArenaAlloc; // for access to ctor 190 Processor(GrAAType aaType,Flags flags)191 Processor(GrAAType aaType, Flags flags) 192 : INHERITED(kGrFillRRectOp_Processor_ClassID) 193 , fAAType(aaType) 194 , fFlags(flags) { 195 int numVertexAttribs = (GrAAType::kCoverage == fAAType) ? 3 : 2; 196 this->setVertexAttributes(kVertexAttribs, numVertexAttribs); 197 198 if (!(flags & Flags::kHasPerspective)) { 199 // Affine 2D transformation (float2x2 plus float2 translate). 200 fInstanceAttribs.emplace_back("skew", kFloat4_GrVertexAttribType, kFloat4_GrSLType); 201 fInstanceAttribs.emplace_back( 202 "translate", kFloat2_GrVertexAttribType, kFloat2_GrSLType); 203 } else { 204 // Perspective float3x3 transformation matrix. 205 fInstanceAttribs.emplace_back("persp_x", kFloat3_GrVertexAttribType, kFloat3_GrSLType); 206 fInstanceAttribs.emplace_back("persp_y", kFloat3_GrVertexAttribType, kFloat3_GrSLType); 207 fInstanceAttribs.emplace_back("persp_z", kFloat3_GrVertexAttribType, kFloat3_GrSLType); 208 } 209 fInstanceAttribs.emplace_back("radii_x", kFloat4_GrVertexAttribType, kFloat4_GrSLType); 210 fInstanceAttribs.emplace_back("radii_y", kFloat4_GrVertexAttribType, kFloat4_GrSLType); 211 fColorAttrib = &fInstanceAttribs.push_back( 212 MakeColorAttribute("color", (flags & Flags::kWideColor))); 213 if (fFlags & Flags::kHasLocalCoords) { 214 fInstanceAttribs.emplace_back( 215 "local_rect", kFloat4_GrVertexAttribType, kFloat4_GrSLType); 216 } 217 this->setInstanceAttributes(fInstanceAttribs.begin(), fInstanceAttribs.count()); 218 219 if (GrAAType::kMSAA == fAAType) { 220 this->setWillUseCustomFeature(CustomFeatures::kSampleLocations); 221 } 222 } 223 224 static constexpr Attribute kVertexAttribs[] = { 225 {"radii_selector", kFloat4_GrVertexAttribType, kFloat4_GrSLType}, 226 {"corner_and_radius_outsets", kFloat4_GrVertexAttribType, kFloat4_GrSLType}, 227 // Coverage only. 228 {"aa_bloat_and_coverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType}}; 229 230 const GrAAType fAAType; 231 const Flags fFlags; 232 233 SkSTArray<6, Attribute> fInstanceAttribs; 234 const Attribute* fColorAttrib; 235 236 class CoverageImpl; 237 class MSAAImpl; 238 239 typedef GrGeometryProcessor INHERITED; 240 }; 241 242 constexpr GrPrimitiveProcessor::Attribute GrFillRRectOp::Processor::kVertexAttribs[]; 243 244 // Our coverage geometry consists of an inset octagon with solid coverage, surrounded by linear 245 // coverage ramps on the horizontal and vertical edges, and "arc coverage" pieces on the diagonal 246 // edges. The Vertex struct tells the shader where to place its vertex within a normalized 247 // ([l, t, r, b] = [-1, -1, +1, +1]) space, and how to calculate coverage. See onEmitCode. 248 struct CoverageVertex { 249 std::array<float, 4> fRadiiSelector; 250 std::array<float, 2> fCorner; 251 std::array<float, 2> fRadiusOutset; 252 std::array<float, 2> fAABloatDirection; 253 float fCoverage; 254 float fIsLinearCoverage; 255 }; 256 257 // This is the offset (when multiplied by radii) from the corners of a bounding box to the vertices 258 // of its inscribed octagon. We draw the outside portion of arcs with quarter-octagons rather than 259 // rectangles. 260 static constexpr float kOctoOffset = 1/(1 + SK_ScalarRoot2Over2); 261 262 static constexpr CoverageVertex kCoverageVertexData[] = { 263 // Left inset edge. 264 {{{0,0,0,1}}, {{-1,+1}}, {{0,-1}}, {{+1,0}}, 1, 1}, 265 {{{1,0,0,0}}, {{-1,-1}}, {{0,+1}}, {{+1,0}}, 1, 1}, 266 267 // Top inset edge. 268 {{{1,0,0,0}}, {{-1,-1}}, {{+1,0}}, {{0,+1}}, 1, 1}, 269 {{{0,1,0,0}}, {{+1,-1}}, {{-1,0}}, {{0,+1}}, 1, 1}, 270 271 // Right inset edge. 272 {{{0,1,0,0}}, {{+1,-1}}, {{0,+1}}, {{-1,0}}, 1, 1}, 273 {{{0,0,1,0}}, {{+1,+1}}, {{0,-1}}, {{-1,0}}, 1, 1}, 274 275 // Bottom inset edge. 276 {{{0,0,1,0}}, {{+1,+1}}, {{-1,0}}, {{0,-1}}, 1, 1}, 277 {{{0,0,0,1}}, {{-1,+1}}, {{+1,0}}, {{0,-1}}, 1, 1}, 278 279 280 // Left outset edge. 281 {{{0,0,0,1}}, {{-1,+1}}, {{0,-1}}, {{-1,0}}, 0, 1}, 282 {{{1,0,0,0}}, {{-1,-1}}, {{0,+1}}, {{-1,0}}, 0, 1}, 283 284 // Top outset edge. 285 {{{1,0,0,0}}, {{-1,-1}}, {{+1,0}}, {{0,-1}}, 0, 1}, 286 {{{0,1,0,0}}, {{+1,-1}}, {{-1,0}}, {{0,-1}}, 0, 1}, 287 288 // Right outset edge. 289 {{{0,1,0,0}}, {{+1,-1}}, {{0,+1}}, {{+1,0}}, 0, 1}, 290 {{{0,0,1,0}}, {{+1,+1}}, {{0,-1}}, {{+1,0}}, 0, 1}, 291 292 // Bottom outset edge. 293 {{{0,0,1,0}}, {{+1,+1}}, {{-1,0}}, {{0,+1}}, 0, 1}, 294 {{{0,0,0,1}}, {{-1,+1}}, {{+1,0}}, {{0,+1}}, 0, 1}, 295 296 297 // Top-left corner. 298 {{{1,0,0,0}}, {{-1,-1}}, {{ 0,+1}}, {{-1, 0}}, 0, 0}, 299 {{{1,0,0,0}}, {{-1,-1}}, {{ 0,+1}}, {{+1, 0}}, 1, 0}, 300 {{{1,0,0,0}}, {{-1,-1}}, {{+1, 0}}, {{ 0,+1}}, 1, 0}, 301 {{{1,0,0,0}}, {{-1,-1}}, {{+1, 0}}, {{ 0,-1}}, 0, 0}, 302 {{{1,0,0,0}}, {{-1,-1}}, {{+kOctoOffset,0}}, {{-1,-1}}, 0, 0}, 303 {{{1,0,0,0}}, {{-1,-1}}, {{0,+kOctoOffset}}, {{-1,-1}}, 0, 0}, 304 305 // Top-right corner. 306 {{{0,1,0,0}}, {{+1,-1}}, {{-1, 0}}, {{ 0,-1}}, 0, 0}, 307 {{{0,1,0,0}}, {{+1,-1}}, {{-1, 0}}, {{ 0,+1}}, 1, 0}, 308 {{{0,1,0,0}}, {{+1,-1}}, {{ 0,+1}}, {{-1, 0}}, 1, 0}, 309 {{{0,1,0,0}}, {{+1,-1}}, {{ 0,+1}}, {{+1, 0}}, 0, 0}, 310 {{{0,1,0,0}}, {{+1,-1}}, {{0,+kOctoOffset}}, {{+1,-1}}, 0, 0}, 311 {{{0,1,0,0}}, {{+1,-1}}, {{-kOctoOffset,0}}, {{+1,-1}}, 0, 0}, 312 313 // Bottom-right corner. 314 {{{0,0,1,0}}, {{+1,+1}}, {{ 0,-1}}, {{+1, 0}}, 0, 0}, 315 {{{0,0,1,0}}, {{+1,+1}}, {{ 0,-1}}, {{-1, 0}}, 1, 0}, 316 {{{0,0,1,0}}, {{+1,+1}}, {{-1, 0}}, {{ 0,-1}}, 1, 0}, 317 {{{0,0,1,0}}, {{+1,+1}}, {{-1, 0}}, {{ 0,+1}}, 0, 0}, 318 {{{0,0,1,0}}, {{+1,+1}}, {{-kOctoOffset,0}}, {{+1,+1}}, 0, 0}, 319 {{{0,0,1,0}}, {{+1,+1}}, {{0,-kOctoOffset}}, {{+1,+1}}, 0, 0}, 320 321 // Bottom-left corner. 322 {{{0,0,0,1}}, {{-1,+1}}, {{+1, 0}}, {{ 0,+1}}, 0, 0}, 323 {{{0,0,0,1}}, {{-1,+1}}, {{+1, 0}}, {{ 0,-1}}, 1, 0}, 324 {{{0,0,0,1}}, {{-1,+1}}, {{ 0,-1}}, {{+1, 0}}, 1, 0}, 325 {{{0,0,0,1}}, {{-1,+1}}, {{ 0,-1}}, {{-1, 0}}, 0, 0}, 326 {{{0,0,0,1}}, {{-1,+1}}, {{0,-kOctoOffset}}, {{-1,+1}}, 0, 0}, 327 {{{0,0,0,1}}, {{-1,+1}}, {{+kOctoOffset,0}}, {{-1,+1}}, 0, 0}}; 328 329 GR_DECLARE_STATIC_UNIQUE_KEY(gCoverageVertexBufferKey); 330 331 static constexpr uint16_t kCoverageIndexData[] = { 332 // Inset octagon (solid coverage). 333 0, 1, 7, 334 1, 2, 7, 335 7, 2, 6, 336 2, 3, 6, 337 6, 3, 5, 338 3, 4, 5, 339 340 // AA borders (linear coverage). 341 0, 1, 8, 1, 9, 8, 342 2, 3, 10, 3, 11, 10, 343 4, 5, 12, 5, 13, 12, 344 6, 7, 14, 7, 15, 14, 345 346 // Top-left arc. 347 16, 17, 21, 348 17, 21, 18, 349 21, 18, 20, 350 18, 20, 19, 351 352 // Top-right arc. 353 22, 23, 27, 354 23, 27, 24, 355 27, 24, 26, 356 24, 26, 25, 357 358 // Bottom-right arc. 359 28, 29, 33, 360 29, 33, 30, 361 33, 30, 32, 362 30, 32, 31, 363 364 // Bottom-left arc. 365 34, 35, 39, 366 35, 39, 36, 367 39, 36, 38, 368 36, 38, 37}; 369 370 GR_DECLARE_STATIC_UNIQUE_KEY(gCoverageIndexBufferKey); 371 372 373 // Our MSAA geometry consists of an inset octagon with full sample mask coverage, circumscribed 374 // by a larger octagon that modifies the sample mask for the arc at each corresponding corner. 375 struct MSAAVertex { 376 std::array<float, 4> fRadiiSelector; 377 std::array<float, 2> fCorner; 378 std::array<float, 2> fRadiusOutset; 379 }; 380 381 static constexpr MSAAVertex kMSAAVertexData[] = { 382 // Left edge. (Negative radii selector indicates this is not an arc section.) 383 {{{0,0,0,-1}}, {{-1,+1}}, {{0,-1}}}, 384 {{{-1,0,0,0}}, {{-1,-1}}, {{0,+1}}}, 385 386 // Top edge. 387 {{{-1,0,0,0}}, {{-1,-1}}, {{+1,0}}}, 388 {{{0,-1,0,0}}, {{+1,-1}}, {{-1,0}}}, 389 390 // Right edge. 391 {{{0,-1,0,0}}, {{+1,-1}}, {{0,+1}}}, 392 {{{0,0,-1,0}}, {{+1,+1}}, {{0,-1}}}, 393 394 // Bottom edge. 395 {{{0,0,-1,0}}, {{+1,+1}}, {{-1,0}}}, 396 {{{0,0,0,-1}}, {{-1,+1}}, {{+1,0}}}, 397 398 // Top-left corner. 399 {{{1,0,0,0}}, {{-1,-1}}, {{0,+1}}}, 400 {{{1,0,0,0}}, {{-1,-1}}, {{0,+kOctoOffset}}}, 401 {{{1,0,0,0}}, {{-1,-1}}, {{+1,0}}}, 402 {{{1,0,0,0}}, {{-1,-1}}, {{+kOctoOffset,0}}}, 403 404 // Top-right corner. 405 {{{0,1,0,0}}, {{+1,-1}}, {{-1,0}}}, 406 {{{0,1,0,0}}, {{+1,-1}}, {{-kOctoOffset,0}}}, 407 {{{0,1,0,0}}, {{+1,-1}}, {{0,+1}}}, 408 {{{0,1,0,0}}, {{+1,-1}}, {{0,+kOctoOffset}}}, 409 410 // Bottom-right corner. 411 {{{0,0,1,0}}, {{+1,+1}}, {{0,-1}}}, 412 {{{0,0,1,0}}, {{+1,+1}}, {{0,-kOctoOffset}}}, 413 {{{0,0,1,0}}, {{+1,+1}}, {{-1,0}}}, 414 {{{0,0,1,0}}, {{+1,+1}}, {{-kOctoOffset,0}}}, 415 416 // Bottom-left corner. 417 {{{0,0,0,1}}, {{-1,+1}}, {{+1,0}}}, 418 {{{0,0,0,1}}, {{-1,+1}}, {{+kOctoOffset,0}}}, 419 {{{0,0,0,1}}, {{-1,+1}}, {{0,-1}}}, 420 {{{0,0,0,1}}, {{-1,+1}}, {{0,-kOctoOffset}}}}; 421 422 GR_DECLARE_STATIC_UNIQUE_KEY(gMSAAVertexBufferKey); 423 424 static constexpr uint16_t kMSAAIndexData[] = { 425 // Inset octagon. (Full sample mask.) 426 0, 1, 2, 427 0, 2, 3, 428 0, 3, 6, 429 3, 4, 5, 430 3, 5, 6, 431 6, 7, 0, 432 433 // Top-left arc. (Sample mask is set to the arc.) 434 8, 9, 10, 435 9, 11, 10, 436 437 // Top-right arc. 438 12, 13, 14, 439 13, 15, 14, 440 441 // Bottom-right arc. 442 16, 17, 18, 443 17, 19, 18, 444 445 // Bottom-left arc. 446 20, 21, 22, 447 21, 23, 22}; 448 449 GR_DECLARE_STATIC_UNIQUE_KEY(gMSAAIndexBufferKey); 450 onPrePrepare(GrRecordingContext * context,const GrSurfaceProxyView * dstView,GrAppliedClip * clip,const GrXferProcessor::DstProxyView & dstProxyView)451 void GrFillRRectOp::onPrePrepare(GrRecordingContext* context, 452 const GrSurfaceProxyView* dstView, 453 GrAppliedClip* clip, 454 const GrXferProcessor::DstProxyView& dstProxyView) { 455 SkArenaAlloc* arena = context->priv().recordTimeAllocator(); 456 457 // This is equivalent to a GrOpFlushState::detachAppliedClip 458 GrAppliedClip appliedClip = clip ? std::move(*clip) : GrAppliedClip(); 459 460 // TODO: it would be cool if, right here, we created both the program info and desc 461 // in the record-time arena. Then, if the program info had already been seen, we could 462 // get pointers back to the prior versions and be able to return the allocated space 463 // back to the arena. 464 fProgramInfo = this->createProgramInfo(context->priv().caps(), arena, dstView, 465 std::move(appliedClip), dstProxyView); 466 467 context->priv().recordProgramInfo(fProgramInfo); 468 } 469 onPrepare(GrOpFlushState * flushState)470 void GrFillRRectOp::onPrepare(GrOpFlushState* flushState) { 471 if (void* instanceData = flushState->makeVertexSpace(fInstanceStride, fInstanceCount, 472 &fInstanceBuffer, &fBaseInstance)) { 473 SkASSERT(fInstanceStride * fInstanceCount == fInstanceData.count()); 474 memcpy(instanceData, fInstanceData.begin(), fInstanceData.count()); 475 } 476 477 if (GrAAType::kCoverage == fAAType) { 478 GR_DEFINE_STATIC_UNIQUE_KEY(gCoverageIndexBufferKey); 479 480 fIndexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer( 481 GrGpuBufferType::kIndex, sizeof(kCoverageIndexData), kCoverageIndexData, 482 gCoverageIndexBufferKey); 483 484 GR_DEFINE_STATIC_UNIQUE_KEY(gCoverageVertexBufferKey); 485 486 fVertexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer( 487 GrGpuBufferType::kVertex, sizeof(kCoverageVertexData), kCoverageVertexData, 488 gCoverageVertexBufferKey); 489 490 fIndexCount = SK_ARRAY_COUNT(kCoverageIndexData); 491 } else { 492 GR_DEFINE_STATIC_UNIQUE_KEY(gMSAAIndexBufferKey); 493 494 fIndexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer( 495 GrGpuBufferType::kIndex, sizeof(kMSAAIndexData), kMSAAIndexData, 496 gMSAAIndexBufferKey); 497 498 GR_DEFINE_STATIC_UNIQUE_KEY(gMSAAVertexBufferKey); 499 500 fVertexBuffer = flushState->resourceProvider()->findOrMakeStaticBuffer( 501 GrGpuBufferType::kVertex, sizeof(kMSAAVertexData), kMSAAVertexData, 502 gMSAAVertexBufferKey); 503 504 fIndexCount = SK_ARRAY_COUNT(kMSAAIndexData); 505 } 506 } 507 508 class GrFillRRectOp::Processor::CoverageImpl : public GrGLSLGeometryProcessor { onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)509 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { 510 const auto& proc = args.fGP.cast<Processor>(); 511 bool useHWDerivatives = (proc.fFlags & Flags::kUseHWDerivatives); 512 513 SkASSERT(proc.vertexStride() == sizeof(CoverageVertex)); 514 515 GrGLSLVaryingHandler* varyings = args.fVaryingHandler; 516 varyings->emitAttributes(proc); 517 varyings->addPassThroughAttribute(*proc.fColorAttrib, args.fOutputColor, 518 GrGLSLVaryingHandler::Interpolation::kCanBeFlat); 519 520 // Emit the vertex shader. 521 GrGLSLVertexBuilder* v = args.fVertBuilder; 522 523 // Unpack vertex attribs. 524 v->codeAppend("float2 corner = corner_and_radius_outsets.xy;"); 525 v->codeAppend("float2 radius_outset = corner_and_radius_outsets.zw;"); 526 v->codeAppend("float2 aa_bloat_direction = aa_bloat_and_coverage.xy;"); 527 v->codeAppend("float coverage = aa_bloat_and_coverage.z;"); 528 v->codeAppend("float is_linear_coverage = aa_bloat_and_coverage.w;"); 529 530 // Find the amount to bloat each edge for AA (in source space). 531 v->codeAppend("float2 pixellength = inversesqrt(" 532 "float2(dot(skew.xz, skew.xz), dot(skew.yw, skew.yw)));"); 533 v->codeAppend("float4 normalized_axis_dirs = skew * pixellength.xyxy;"); 534 v->codeAppend("float2 axiswidths = (abs(normalized_axis_dirs.xy) + " 535 "abs(normalized_axis_dirs.zw));"); 536 v->codeAppend("float2 aa_bloatradius = axiswidths * pixellength * .5;"); 537 538 // Identify our radii. 539 v->codeAppend("float4 radii_and_neighbors = radii_selector" 540 "* float4x4(radii_x, radii_y, radii_x.yxwz, radii_y.wzyx);"); 541 v->codeAppend("float2 radii = radii_and_neighbors.xy;"); 542 v->codeAppend("float2 neighbor_radii = radii_and_neighbors.zw;"); 543 544 v->codeAppend("if (any(greaterThan(aa_bloatradius, float2(1)))) {"); 545 // The rrect is more narrow than an AA coverage ramp. We can't draw as-is 546 // or else opposite AA borders will overlap. Instead, fudge the size up to 547 // the width of a coverage ramp, and then reduce total coverage to make 548 // the rect appear more thin. 549 v->codeAppend( "corner = max(abs(corner), aa_bloatradius) * sign(corner);"); 550 v->codeAppend( "coverage /= max(aa_bloatradius.x, 1) * max(aa_bloatradius.y, 1);"); 551 // Set radii to zero to ensure we take the "linear coverage" codepath. 552 // (The "coverage" variable only has effect in the linear codepath.) 553 v->codeAppend( "radii = float2(0);"); 554 v->codeAppend("}"); 555 556 v->codeAppend("if (any(lessThan(radii, aa_bloatradius * 1.25))) {"); 557 // The radii are very small. Demote this arc to a sharp 90 degree corner. 558 v->codeAppend( "radii = aa_bloatradius;"); 559 // Snap octagon vertices to the corner of the bounding box. 560 v->codeAppend( "radius_outset = floor(abs(radius_outset)) * radius_outset;"); 561 v->codeAppend( "is_linear_coverage = 1;"); 562 v->codeAppend("} else {"); 563 // Don't let radii get smaller than a pixel. 564 v->codeAppend( "radii = clamp(radii, pixellength, 2 - pixellength);"); 565 v->codeAppend( "neighbor_radii = clamp(neighbor_radii, pixellength, 2 - pixellength);"); 566 // Don't let neighboring radii get closer together than 1/16 pixel. 567 v->codeAppend( "float2 spacing = 2 - radii - neighbor_radii;"); 568 v->codeAppend( "float2 extra_pad = max(pixellength * .0625 - spacing, float2(0));"); 569 v->codeAppend( "radii -= extra_pad * .5;"); 570 v->codeAppend("}"); 571 572 // Find our vertex position, adjusted for radii and bloated for AA. Our rect is drawn in 573 // normalized [-1,-1,+1,+1] space. 574 v->codeAppend("float2 aa_outset = aa_bloat_direction.xy * aa_bloatradius;"); 575 v->codeAppend("float2 vertexpos = corner + radius_outset * radii + aa_outset;"); 576 577 // Emit transforms. 578 GrShaderVar localCoord("", kFloat2_GrSLType); 579 if (proc.fFlags & Flags::kHasLocalCoords) { 580 v->codeAppend("float2 localcoord = (local_rect.xy * (1 - vertexpos) + " 581 "local_rect.zw * (1 + vertexpos)) * .5;"); 582 localCoord.set(kFloat2_GrSLType, "localcoord"); 583 } 584 this->emitTransforms(v, varyings, args.fUniformHandler, localCoord, 585 args.fFPCoordTransformHandler); 586 587 // Transform to device space. 588 SkASSERT(!(proc.fFlags & Flags::kHasPerspective)); 589 v->codeAppend("float2x2 skewmatrix = float2x2(skew.xy, skew.zw);"); 590 v->codeAppend("float2 devcoord = vertexpos * skewmatrix + translate;"); 591 gpArgs->fPositionVar.set(kFloat2_GrSLType, "devcoord"); 592 593 // Setup interpolants for coverage. 594 GrGLSLVarying arcCoord(useHWDerivatives ? kFloat2_GrSLType : kFloat4_GrSLType); 595 varyings->addVarying("arccoord", &arcCoord); 596 v->codeAppend("if (0 != is_linear_coverage) {"); 597 // We are a non-corner piece: Set x=0 to indicate built-in coverage, and 598 // interpolate linear coverage across y. 599 v->codeAppendf( "%s.xy = float2(0, coverage);", arcCoord.vsOut()); 600 v->codeAppend("} else {"); 601 // Find the normalized arc coordinates for our corner ellipse. 602 // (i.e., the coordinate system where x^2 + y^2 == 1). 603 v->codeAppend( "float2 arccoord = 1 - abs(radius_outset) + aa_outset/radii * corner;"); 604 // We are a corner piece: Interpolate the arc coordinates for coverage. 605 // Emit x+1 to ensure no pixel in the arc has a x value of 0 (since x=0 606 // instructs the fragment shader to use linear coverage). 607 v->codeAppendf( "%s.xy = float2(arccoord.x+1, arccoord.y);", arcCoord.vsOut()); 608 if (!useHWDerivatives) { 609 // The gradient is order-1: Interpolate it across arccoord.zw. 610 v->codeAppendf("float2x2 derivatives = inverse(skewmatrix);"); 611 v->codeAppendf("%s.zw = derivatives * (arccoord/radii * 2);", arcCoord.vsOut()); 612 } 613 v->codeAppend("}"); 614 615 // Emit the fragment shader. 616 GrGLSLFPFragmentBuilder* f = args.fFragBuilder; 617 618 f->codeAppendf("float x_plus_1=%s.x, y=%s.y;", arcCoord.fsIn(), arcCoord.fsIn()); 619 f->codeAppendf("half coverage;"); 620 f->codeAppendf("if (0 == x_plus_1) {"); 621 f->codeAppendf( "coverage = half(y);"); // We are a non-arc pixel (linear coverage). 622 f->codeAppendf("} else {"); 623 f->codeAppendf( "float fn = x_plus_1 * (x_plus_1 - 2);"); // fn = (x+1)*(x-1) = x^2-1 624 f->codeAppendf( "fn = fma(y,y, fn);"); // fn = x^2 + y^2 - 1 625 if (useHWDerivatives) { 626 f->codeAppendf("float fnwidth = fwidth(fn);"); 627 } else { 628 // The gradient is interpolated across arccoord.zw. 629 f->codeAppendf("float gx=%s.z, gy=%s.w;", arcCoord.fsIn(), arcCoord.fsIn()); 630 f->codeAppendf("float fnwidth = abs(gx) + abs(gy);"); 631 } 632 f->codeAppendf( "half d = half(fn/fnwidth);"); 633 f->codeAppendf( "coverage = clamp(.5 - d, 0, 1);"); 634 f->codeAppendf("}"); 635 f->codeAppendf("%s = half4(coverage);", args.fOutputCoverage); 636 } 637 setData(const GrGLSLProgramDataManager & pdman,const GrPrimitiveProcessor &,const CoordTransformRange & transformRange)638 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&, 639 const CoordTransformRange& transformRange) override { 640 this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange); 641 } 642 }; 643 644 645 class GrFillRRectOp::Processor::MSAAImpl : public GrGLSLGeometryProcessor { onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)646 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { 647 const auto& proc = args.fGP.cast<Processor>(); 648 bool useHWDerivatives = (proc.fFlags & Flags::kUseHWDerivatives); 649 bool hasPerspective = (proc.fFlags & Flags::kHasPerspective); 650 bool hasLocalCoords = (proc.fFlags & Flags::kHasLocalCoords); 651 SkASSERT(useHWDerivatives == hasPerspective); 652 653 SkASSERT(proc.vertexStride() == sizeof(MSAAVertex)); 654 655 // Emit the vertex shader. 656 GrGLSLVertexBuilder* v = args.fVertBuilder; 657 658 GrGLSLVaryingHandler* varyings = args.fVaryingHandler; 659 varyings->emitAttributes(proc); 660 varyings->addPassThroughAttribute(*proc.fColorAttrib, args.fOutputColor, 661 GrGLSLVaryingHandler::Interpolation::kCanBeFlat); 662 663 // Unpack vertex attribs. 664 v->codeAppendf("float2 corner = corner_and_radius_outsets.xy;"); 665 v->codeAppendf("float2 radius_outset = corner_and_radius_outsets.zw;"); 666 667 // Identify our radii. 668 v->codeAppend("float2 radii;"); 669 v->codeAppend("radii.x = dot(radii_selector, radii_x);"); 670 v->codeAppend("radii.y = dot(radii_selector, radii_y);"); 671 v->codeAppendf("bool is_arc_section = (radii.x > 0);"); 672 v->codeAppendf("radii = abs(radii);"); 673 674 // Find our vertex position, adjusted for radii. Our rect is drawn in normalized 675 // [-1,-1,+1,+1] space. 676 v->codeAppend("float2 vertexpos = corner + radius_outset * radii;"); 677 678 // Emit transforms. 679 GrShaderVar localCoord("", kFloat2_GrSLType); 680 if (hasLocalCoords) { 681 v->codeAppend("float2 localcoord = (local_rect.xy * (1 - vertexpos) + " 682 "local_rect.zw * (1 + vertexpos)) * .5;"); 683 localCoord.set(kFloat2_GrSLType, "localcoord"); 684 } 685 this->emitTransforms(v, varyings, args.fUniformHandler, localCoord, 686 args.fFPCoordTransformHandler); 687 688 // Transform to device space. 689 if (!hasPerspective) { 690 v->codeAppend("float2x2 skewmatrix = float2x2(skew.xy, skew.zw);"); 691 v->codeAppend("float2 devcoord = vertexpos * skewmatrix + translate;"); 692 gpArgs->fPositionVar.set(kFloat2_GrSLType, "devcoord"); 693 } else { 694 v->codeAppend("float3x3 persp_matrix = float3x3(persp_x, persp_y, persp_z);"); 695 v->codeAppend("float3 devcoord = float3(vertexpos, 1) * persp_matrix;"); 696 gpArgs->fPositionVar.set(kFloat3_GrSLType, "devcoord"); 697 } 698 699 // Determine normalized arc coordinates for the implicit function. 700 GrGLSLVarying arcCoord((useHWDerivatives) ? kFloat2_GrSLType : kFloat4_GrSLType); 701 varyings->addVarying("arccoord", &arcCoord); 702 v->codeAppendf("if (is_arc_section) {"); 703 v->codeAppendf( "%s.xy = 1 - abs(radius_outset);", arcCoord.vsOut()); 704 if (!useHWDerivatives) { 705 // The gradient is order-1: Interpolate it across arccoord.zw. 706 // This doesn't work with perspective. 707 SkASSERT(!hasPerspective); 708 v->codeAppendf("float2x2 derivatives = inverse(skewmatrix);"); 709 v->codeAppendf("%s.zw = derivatives * (%s.xy/radii * corner * 2);", 710 arcCoord.vsOut(), arcCoord.vsOut()); 711 } 712 v->codeAppendf("} else {"); 713 if (useHWDerivatives) { 714 v->codeAppendf("%s = float2(0);", arcCoord.vsOut()); 715 } else { 716 v->codeAppendf("%s = float4(0);", arcCoord.vsOut()); 717 } 718 v->codeAppendf("}"); 719 720 // Emit the fragment shader. 721 GrGLSLFPFragmentBuilder* f = args.fFragBuilder; 722 723 f->codeAppendf("%s = half4(1);", args.fOutputCoverage); 724 725 // If x,y == 0, then we are drawing a triangle that does not track an arc. 726 f->codeAppendf("if (float2(0) != %s.xy) {", arcCoord.fsIn()); 727 f->codeAppendf( "float fn = dot(%s.xy, %s.xy) - 1;", arcCoord.fsIn(), arcCoord.fsIn()); 728 if (GrAAType::kMSAA == proc.fAAType) { 729 using ScopeFlags = GrGLSLFPFragmentBuilder::ScopeFlags; 730 if (!useHWDerivatives) { 731 f->codeAppendf("float2 grad = %s.zw;", arcCoord.fsIn()); 732 f->applyFnToMultisampleMask("fn", "grad", ScopeFlags::kInsidePerPrimitiveBranch); 733 } else { 734 f->applyFnToMultisampleMask("fn", nullptr, ScopeFlags::kInsidePerPrimitiveBranch); 735 } 736 } else { 737 f->codeAppendf("if (fn > 0) {"); 738 f->codeAppendf( "%s = half4(0);", args.fOutputCoverage); 739 f->codeAppendf("}"); 740 } 741 f->codeAppendf("}"); 742 } 743 setData(const GrGLSLProgramDataManager & pdman,const GrPrimitiveProcessor &,const CoordTransformRange & transformRange)744 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&, 745 const CoordTransformRange& transformRange) override { 746 this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange); 747 } 748 }; 749 createGLSLInstance(const GrShaderCaps &) const750 GrGLSLPrimitiveProcessor* GrFillRRectOp::Processor::createGLSLInstance( 751 const GrShaderCaps&) const { 752 if (GrAAType::kCoverage != fAAType) { 753 return new MSAAImpl(); 754 } 755 return new CoverageImpl(); 756 } 757 createProgramInfo(const GrCaps * caps,SkArenaAlloc * arena,const GrSurfaceProxyView * dstView,GrAppliedClip && appliedClip,const GrXferProcessor::DstProxyView & dstProxyView)758 GrProgramInfo* GrFillRRectOp::createProgramInfo(const GrCaps* caps, 759 SkArenaAlloc* arena, 760 const GrSurfaceProxyView* dstView, 761 GrAppliedClip&& appliedClip, 762 const GrXferProcessor::DstProxyView& dstProxyView) { 763 GrGeometryProcessor* geomProc = Processor::Make(arena, fAAType, fFlags); 764 SkASSERT(geomProc->instanceStride() == (size_t)fInstanceStride); 765 766 GrPipeline::InitArgs initArgs; 767 if (GrAAType::kMSAA == fAAType) { 768 initArgs.fInputFlags = GrPipeline::InputFlags::kHWAntialias; 769 } 770 initArgs.fCaps = caps; 771 initArgs.fDstProxyView = dstProxyView; 772 initArgs.fOutputSwizzle = dstView->swizzle(); 773 774 GrPipeline::FixedDynamicState* fixedDynamicState = nullptr; 775 776 if (appliedClip.scissorState().enabled()) { 777 fixedDynamicState = arena->make<GrPipeline::FixedDynamicState>( 778 appliedClip.scissorState().rect()); 779 } 780 781 GrPipeline* pipeline = arena->make<GrPipeline>(initArgs, 782 std::move(fProcessors), 783 std::move(appliedClip)); 784 785 GrRenderTargetProxy* dstProxy = dstView->asRenderTargetProxy(); 786 return arena->make<GrProgramInfo>(dstProxy->numSamples(), 787 dstProxy->numStencilSamples(), 788 dstProxy->backendFormat(), 789 dstView->origin(), 790 pipeline, 791 geomProc, 792 fixedDynamicState, 793 nullptr, 0, 794 GrPrimitiveType::kTriangles); 795 } 796 onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)797 void GrFillRRectOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) { 798 if (!fInstanceBuffer || !fIndexBuffer || !fVertexBuffer) { 799 return; // Setup failed. 800 } 801 802 if (!fProgramInfo) { 803 const GrSurfaceProxyView* dstView = flushState->view(); 804 805 fProgramInfo = this->createProgramInfo(&flushState->caps(), 806 flushState->allocator(), 807 dstView, 808 flushState->detachAppliedClip(), 809 flushState->dstProxyView()); 810 } 811 812 GrMesh* mesh = flushState->allocator()->make<GrMesh>(); 813 mesh->setIndexedInstanced(std::move(fIndexBuffer), fIndexCount, 814 std::move(fInstanceBuffer), fInstanceCount, 815 fBaseInstance, GrPrimitiveRestart::kNo); 816 mesh->setVertexData(std::move(fVertexBuffer)); 817 818 flushState->opsRenderPass()->bindPipeline(*fProgramInfo, this->bounds()); 819 flushState->opsRenderPass()->drawMeshes(*fProgramInfo, mesh, 1); 820 } 821 822 // Will the given corner look good if we use HW derivatives? can_use_hw_derivatives_with_coverage(const Sk2f & devScale,const Sk2f & cornerRadii)823 static bool can_use_hw_derivatives_with_coverage(const Sk2f& devScale, const Sk2f& cornerRadii) { 824 Sk2f devRadii = devScale * cornerRadii; 825 if (devRadii[1] < devRadii[0]) { 826 devRadii = SkNx_shuffle<1,0>(devRadii); 827 } 828 float minDevRadius = std::max(devRadii[0], 1.f); // Shader clamps radius at a minimum of 1. 829 // Is the gradient smooth enough for this corner look ok if we use hardware derivatives? 830 // This threshold was arrived at subjevtively on an NVIDIA chip. 831 return minDevRadius * minDevRadius * 5 > devRadii[1]; 832 } 833 can_use_hw_derivatives_with_coverage(const Sk2f & devScale,const SkVector & cornerRadii)834 static bool can_use_hw_derivatives_with_coverage( 835 const Sk2f& devScale, const SkVector& cornerRadii) { 836 return can_use_hw_derivatives_with_coverage(devScale, Sk2f::Load(&cornerRadii)); 837 } 838 839 // Will the given round rect look good if we use HW derivatives? can_use_hw_derivatives_with_coverage(const GrShaderCaps & shaderCaps,const SkMatrix & viewMatrix,const SkRRect & rrect)840 static bool can_use_hw_derivatives_with_coverage( 841 const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix, const SkRRect& rrect) { 842 if (!shaderCaps.shaderDerivativeSupport()) { 843 return false; 844 } 845 846 Sk2f x = Sk2f(viewMatrix.getScaleX(), viewMatrix.getSkewX()); 847 Sk2f y = Sk2f(viewMatrix.getSkewY(), viewMatrix.getScaleY()); 848 Sk2f devScale = (x*x + y*y).sqrt(); 849 switch (rrect.getType()) { 850 case SkRRect::kEmpty_Type: 851 case SkRRect::kRect_Type: 852 return true; 853 854 case SkRRect::kOval_Type: 855 case SkRRect::kSimple_Type: 856 return can_use_hw_derivatives_with_coverage(devScale, rrect.getSimpleRadii()); 857 858 case SkRRect::kNinePatch_Type: { 859 Sk2f r0 = Sk2f::Load(SkRRectPriv::GetRadiiArray(rrect)); 860 Sk2f r1 = Sk2f::Load(SkRRectPriv::GetRadiiArray(rrect) + 2); 861 Sk2f minRadii = Sk2f::Min(r0, r1); 862 Sk2f maxRadii = Sk2f::Max(r0, r1); 863 return can_use_hw_derivatives_with_coverage(devScale, Sk2f(minRadii[0], maxRadii[1])) && 864 can_use_hw_derivatives_with_coverage(devScale, Sk2f(maxRadii[0], minRadii[1])); 865 } 866 867 case SkRRect::kComplex_Type: { 868 for (int i = 0; i < 4; ++i) { 869 auto corner = static_cast<SkRRect::Corner>(i); 870 if (!can_use_hw_derivatives_with_coverage(devScale, rrect.radii(corner))) { 871 return false; 872 } 873 } 874 return true; 875 } 876 } 877 SK_ABORT("Invalid round rect type."); 878 } 879