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 "GrStrokeRectOp.h" 9 10 #include "GrCaps.h" 11 #include "GrColor.h" 12 #include "GrDefaultGeoProcFactory.h" 13 #include "GrDrawOpTest.h" 14 #include "GrMeshDrawOp.h" 15 #include "GrOpFlushState.h" 16 #include "GrResourceKey.h" 17 #include "GrResourceProvider.h" 18 #include "GrSimpleMeshDrawOpHelper.h" 19 #include "GrVertexWriter.h" 20 #include "ops/GrFillRectOp.h" 21 #include "SkRandom.h" 22 #include "SkStrokeRec.h" 23 24 namespace { 25 26 // We support all hairlines, bevels, and miters, but not round joins. Also, check whether the miter 27 // limit makes a miter join effectively beveled. If the miter is effectively beveled, it is only 28 // supported when using an AA stroke. 29 inline static bool allowed_stroke(const SkStrokeRec& stroke, GrAA aa, bool* isMiter) { 30 SkASSERT(stroke.getStyle() == SkStrokeRec::kStroke_Style || 31 stroke.getStyle() == SkStrokeRec::kHairline_Style); 32 // For hairlines, make bevel and round joins appear the same as mitered ones. 33 if (!stroke.getWidth()) { 34 *isMiter = true; 35 return true; 36 } 37 if (stroke.getJoin() == SkPaint::kBevel_Join) { 38 *isMiter = false; 39 return aa == GrAA::kYes; // bevel only supported with AA 40 } 41 if (stroke.getJoin() == SkPaint::kMiter_Join) { 42 *isMiter = stroke.getMiter() >= SK_ScalarSqrt2; 43 // Supported under non-AA only if it remains mitered 44 return aa == GrAA::kYes || *isMiter; 45 } 46 return false; 47 } 48 49 50 /////////////////////////////////////////////////////////////////////////////////////////////////// 51 // Non-AA Stroking 52 /////////////////////////////////////////////////////////////////////////////////////////////////// 53 54 /* create a triangle strip that strokes the specified rect. There are 8 55 unique vertices, but we repeat the last 2 to close up. Alternatively we 56 could use an indices array, and then only send 8 verts, but not sure that 57 would be faster. 58 */ 59 static void init_nonaa_stroke_rect_strip(SkPoint verts[10], const SkRect& rect, SkScalar width) { 60 const SkScalar rad = SkScalarHalf(width); 61 62 verts[0].set(rect.fLeft + rad, rect.fTop + rad); 63 verts[1].set(rect.fLeft - rad, rect.fTop - rad); 64 verts[2].set(rect.fRight - rad, rect.fTop + rad); 65 verts[3].set(rect.fRight + rad, rect.fTop - rad); 66 verts[4].set(rect.fRight - rad, rect.fBottom - rad); 67 verts[5].set(rect.fRight + rad, rect.fBottom + rad); 68 verts[6].set(rect.fLeft + rad, rect.fBottom - rad); 69 verts[7].set(rect.fLeft - rad, rect.fBottom + rad); 70 verts[8] = verts[0]; 71 verts[9] = verts[1]; 72 73 // TODO: we should be catching this higher up the call stack and just draw a single 74 // non-AA rect 75 if (2*rad >= rect.width()) { 76 verts[0].fX = verts[2].fX = verts[4].fX = verts[6].fX = verts[8].fX = rect.centerX(); 77 } 78 if (2*rad >= rect.height()) { 79 verts[0].fY = verts[2].fY = verts[4].fY = verts[6].fY = verts[8].fY = rect.centerY(); 80 } 81 } 82 83 class NonAAStrokeRectOp final : public GrMeshDrawOp { 84 private: 85 using Helper = GrSimpleMeshDrawOpHelper; 86 87 public: 88 DEFINE_OP_CLASS_ID 89 90 const char* name() const override { return "NonAAStrokeRectOp"; } 91 92 void visitProxies(const VisitProxyFunc& func, VisitorType) const override { 93 fHelper.visitProxies(func); 94 } 95 96 #ifdef SK_DEBUG 97 SkString dumpInfo() const override { 98 SkString string; 99 string.appendf( 100 "Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], " 101 "StrokeWidth: %.2f\n", 102 fColor.toBytes_RGBA(), fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom, 103 fStrokeWidth); 104 string += fHelper.dumpInfo(); 105 string += INHERITED::dumpInfo(); 106 return string; 107 } 108 #endif 109 110 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context, 111 GrPaint&& paint, 112 const SkMatrix& viewMatrix, 113 const SkRect& rect, 114 const SkStrokeRec& stroke, 115 GrAAType aaType) { 116 bool isMiter; 117 if (!allowed_stroke(stroke, GrAA::kNo, &isMiter)) { 118 return nullptr; 119 } 120 Helper::Flags flags = Helper::Flags::kNone; 121 // Depending on sub-pixel coordinates and the particular GPU, we may lose a corner of 122 // hairline rects. We jam all the vertices to pixel centers to avoid this, but not 123 // when MSAA is enabled because it can cause ugly artifacts. 124 if (stroke.getStyle() == SkStrokeRec::kHairline_Style && aaType != GrAAType::kMSAA) { 125 flags |= Helper::Flags::kSnapVerticesToPixelCenters; 126 } 127 return Helper::FactoryHelper<NonAAStrokeRectOp>(context, std::move(paint), flags, 128 viewMatrix, rect, 129 stroke, aaType); 130 } 131 132 NonAAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, 133 Helper::Flags flags, const SkMatrix& viewMatrix, const SkRect& rect, 134 const SkStrokeRec& stroke, GrAAType aaType) 135 : INHERITED(ClassID()), fHelper(helperArgs, aaType, flags) { 136 fColor = color; 137 fViewMatrix = viewMatrix; 138 fRect = rect; 139 // Sort the rect for hairlines 140 fRect.sort(); 141 fStrokeWidth = stroke.getWidth(); 142 143 SkScalar rad = SkScalarHalf(fStrokeWidth); 144 SkRect bounds = rect; 145 bounds.outset(rad, rad); 146 147 // If our caller snaps to pixel centers then we have to round out the bounds 148 if (flags & Helper::Flags::kSnapVerticesToPixelCenters) { 149 viewMatrix.mapRect(&bounds); 150 // We want to be consistent with how we snap non-aa lines. To match what we do in 151 // GrGLSLVertexShaderBuilder, we first floor all the vertex values and then add half a 152 // pixel to force us to pixel centers. 153 bounds.set(SkScalarFloorToScalar(bounds.fLeft), 154 SkScalarFloorToScalar(bounds.fTop), 155 SkScalarFloorToScalar(bounds.fRight), 156 SkScalarFloorToScalar(bounds.fBottom)); 157 bounds.offset(0.5f, 0.5f); 158 this->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo); 159 } else { 160 this->setTransformedBounds(bounds, fViewMatrix, HasAABloat::kNo, IsZeroArea::kNo); 161 } 162 } 163 164 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 165 166 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip, 167 GrFSAAType fsaaType, GrClampType clampType) override { 168 return fHelper.finalizeProcessors( 169 caps, clip, fsaaType, clampType, GrProcessorAnalysisCoverage::kNone, &fColor); 170 } 171 172 private: 173 void onPrepareDraws(Target* target) override { 174 sk_sp<GrGeometryProcessor> gp; 175 { 176 using namespace GrDefaultGeoProcFactory; 177 Color color(fColor); 178 LocalCoords::Type localCoordsType = fHelper.usesLocalCoords() 179 ? LocalCoords::kUsePosition_Type 180 : LocalCoords::kUnused_Type; 181 gp = GrDefaultGeoProcFactory::Make(target->caps().shaderCaps(), color, 182 Coverage::kSolid_Type, localCoordsType, 183 fViewMatrix); 184 } 185 186 size_t kVertexStride = gp->vertexStride(); 187 int vertexCount = kVertsPerHairlineRect; 188 if (fStrokeWidth > 0) { 189 vertexCount = kVertsPerStrokeRect; 190 } 191 192 sk_sp<const GrBuffer> vertexBuffer; 193 int firstVertex; 194 195 void* verts = 196 target->makeVertexSpace(kVertexStride, vertexCount, &vertexBuffer, &firstVertex); 197 198 if (!verts) { 199 SkDebugf("Could not allocate vertices\n"); 200 return; 201 } 202 203 SkPoint* vertex = reinterpret_cast<SkPoint*>(verts); 204 205 GrPrimitiveType primType; 206 if (fStrokeWidth > 0) { 207 primType = GrPrimitiveType::kTriangleStrip; 208 init_nonaa_stroke_rect_strip(vertex, fRect, fStrokeWidth); 209 } else { 210 // hairline 211 primType = GrPrimitiveType::kLineStrip; 212 vertex[0].set(fRect.fLeft, fRect.fTop); 213 vertex[1].set(fRect.fRight, fRect.fTop); 214 vertex[2].set(fRect.fRight, fRect.fBottom); 215 vertex[3].set(fRect.fLeft, fRect.fBottom); 216 vertex[4].set(fRect.fLeft, fRect.fTop); 217 } 218 219 GrMesh* mesh = target->allocMesh(primType); 220 mesh->setNonIndexedNonInstanced(vertexCount); 221 mesh->setVertexData(std::move(vertexBuffer), firstVertex); 222 target->recordDraw(std::move(gp), mesh); 223 } 224 225 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { 226 fHelper.executeDrawsAndUploads(this, flushState, chainBounds); 227 } 228 229 // TODO: override onCombineIfPossible 230 231 Helper fHelper; 232 SkPMColor4f fColor; 233 SkMatrix fViewMatrix; 234 SkRect fRect; 235 SkScalar fStrokeWidth; 236 237 const static int kVertsPerHairlineRect = 5; 238 const static int kVertsPerStrokeRect = 10; 239 240 typedef GrMeshDrawOp INHERITED; 241 }; 242 243 /////////////////////////////////////////////////////////////////////////////////////////////////// 244 // AA Stroking 245 /////////////////////////////////////////////////////////////////////////////////////////////////// 246 247 GR_DECLARE_STATIC_UNIQUE_KEY(gMiterIndexBufferKey); 248 GR_DECLARE_STATIC_UNIQUE_KEY(gBevelIndexBufferKey); 249 250 static void compute_aa_rects(SkRect* devOutside, SkRect* devOutsideAssist, SkRect* devInside, 251 bool* isDegenerate, const SkMatrix& viewMatrix, const SkRect& rect, 252 SkScalar strokeWidth, bool miterStroke) { 253 SkRect devRect; 254 viewMatrix.mapRect(&devRect, rect); 255 256 SkVector devStrokeSize; 257 if (strokeWidth > 0) { 258 devStrokeSize.set(strokeWidth, strokeWidth); 259 viewMatrix.mapVectors(&devStrokeSize, 1); 260 devStrokeSize.setAbs(devStrokeSize); 261 } else { 262 devStrokeSize.set(SK_Scalar1, SK_Scalar1); 263 } 264 265 const SkScalar dx = devStrokeSize.fX; 266 const SkScalar dy = devStrokeSize.fY; 267 const SkScalar rx = SkScalarHalf(dx); 268 const SkScalar ry = SkScalarHalf(dy); 269 270 *devOutside = devRect; 271 *devOutsideAssist = devRect; 272 *devInside = devRect; 273 274 devOutside->outset(rx, ry); 275 devInside->inset(rx, ry); 276 277 // If we have a degenerate stroking rect(ie the stroke is larger than inner rect) then we 278 // make a degenerate inside rect to avoid double hitting. We will also jam all of the points 279 // together when we render these rects. 280 SkScalar spare; 281 { 282 SkScalar w = devRect.width() - dx; 283 SkScalar h = devRect.height() - dy; 284 spare = SkTMin(w, h); 285 } 286 287 *isDegenerate = spare <= 0; 288 if (*isDegenerate) { 289 devInside->fLeft = devInside->fRight = devRect.centerX(); 290 devInside->fTop = devInside->fBottom = devRect.centerY(); 291 } 292 293 // For bevel-stroke, use 2 SkRect instances(devOutside and devOutsideAssist) 294 // to draw the outside of the octagon. Because there are 8 vertices on the outer 295 // edge, while vertex number of inner edge is 4, the same as miter-stroke. 296 if (!miterStroke) { 297 devOutside->inset(0, ry); 298 devOutsideAssist->outset(0, ry); 299 } 300 } 301 302 static sk_sp<GrGeometryProcessor> create_aa_stroke_rect_gp(const GrShaderCaps* shaderCaps, 303 bool tweakAlphaForCoverage, 304 const SkMatrix& viewMatrix, 305 bool usesLocalCoords, 306 bool wideColor) { 307 using namespace GrDefaultGeoProcFactory; 308 309 Coverage::Type coverageType = 310 tweakAlphaForCoverage ? Coverage::kSolid_Type : Coverage::kAttribute_Type; 311 LocalCoords::Type localCoordsType = 312 usesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type; 313 Color::Type colorType = 314 wideColor ? Color::kPremulWideColorAttribute_Type: Color::kPremulGrColorAttribute_Type; 315 316 return MakeForDeviceSpace(shaderCaps, colorType, coverageType, localCoordsType, viewMatrix); 317 } 318 319 class AAStrokeRectOp final : public GrMeshDrawOp { 320 private: 321 using Helper = GrSimpleMeshDrawOpHelper; 322 323 public: 324 DEFINE_OP_CLASS_ID 325 326 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context, 327 GrPaint&& paint, 328 const SkMatrix& viewMatrix, 329 const SkRect& devOutside, 330 const SkRect& devInside) { 331 return Helper::FactoryHelper<AAStrokeRectOp>(context, std::move(paint), viewMatrix, 332 devOutside, devInside); 333 } 334 335 AAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, 336 const SkMatrix& viewMatrix, const SkRect& devOutside, const SkRect& devInside) 337 : INHERITED(ClassID()) 338 , fHelper(helperArgs, GrAAType::kCoverage) 339 , fViewMatrix(viewMatrix) { 340 SkASSERT(!devOutside.isEmpty()); 341 SkASSERT(!devInside.isEmpty()); 342 343 fRects.emplace_back(RectInfo{color, devOutside, devOutside, devInside, false}); 344 this->setBounds(devOutside, HasAABloat::kYes, IsZeroArea::kNo); 345 fMiterStroke = true; 346 fWideColor = !SkPMColor4fFitsInBytes(color); 347 } 348 349 static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context, 350 GrPaint&& paint, 351 const SkMatrix& viewMatrix, 352 const SkRect& rect, 353 const SkStrokeRec& stroke) { 354 bool isMiter; 355 if (!allowed_stroke(stroke, GrAA::kYes, &isMiter)) { 356 return nullptr; 357 } 358 return Helper::FactoryHelper<AAStrokeRectOp>(context, std::move(paint), viewMatrix, rect, 359 stroke, isMiter); 360 } 361 362 AAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color, 363 const SkMatrix& viewMatrix, const SkRect& rect, const SkStrokeRec& stroke, 364 bool isMiter) 365 : INHERITED(ClassID()) 366 , fHelper(helperArgs, GrAAType::kCoverage) 367 , fViewMatrix(viewMatrix) { 368 fMiterStroke = isMiter; 369 fWideColor = !SkPMColor4fFitsInBytes(color); 370 RectInfo& info = fRects.push_back(); 371 compute_aa_rects(&info.fDevOutside, &info.fDevOutsideAssist, &info.fDevInside, 372 &info.fDegenerate, viewMatrix, rect, stroke.getWidth(), isMiter); 373 info.fColor = color; 374 if (isMiter) { 375 this->setBounds(info.fDevOutside, HasAABloat::kYes, IsZeroArea::kNo); 376 } else { 377 // The outer polygon of the bevel stroke is an octagon specified by the points of a 378 // pair of overlapping rectangles where one is wide and the other is narrow. 379 SkRect bounds = info.fDevOutside; 380 bounds.joinPossiblyEmptyRect(info.fDevOutsideAssist); 381 this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo); 382 } 383 } 384 385 const char* name() const override { return "AAStrokeRect"; } 386 387 void visitProxies(const VisitProxyFunc& func, VisitorType) const override { 388 fHelper.visitProxies(func); 389 } 390 391 #ifdef SK_DEBUG 392 SkString dumpInfo() const override { 393 SkString string; 394 for (const auto& info : fRects) { 395 string.appendf( 396 "Color: 0x%08x, ORect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], " 397 "AssistORect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], " 398 "IRect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], Degen: %d", 399 info.fColor.toBytes_RGBA(), info.fDevOutside.fLeft, info.fDevOutside.fTop, 400 info.fDevOutside.fRight, info.fDevOutside.fBottom, info.fDevOutsideAssist.fLeft, 401 info.fDevOutsideAssist.fTop, info.fDevOutsideAssist.fRight, 402 info.fDevOutsideAssist.fBottom, info.fDevInside.fLeft, info.fDevInside.fTop, 403 info.fDevInside.fRight, info.fDevInside.fBottom, info.fDegenerate); 404 } 405 string += fHelper.dumpInfo(); 406 string += INHERITED::dumpInfo(); 407 return string; 408 } 409 #endif 410 411 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } 412 413 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip, 414 GrFSAAType fsaaType, GrClampType clampType) override { 415 return fHelper.finalizeProcessors( 416 caps, clip, fsaaType, clampType, GrProcessorAnalysisCoverage::kSingleChannel, 417 &fRects.back().fColor); 418 } 419 420 private: 421 void onPrepareDraws(Target*) override; 422 void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; 423 424 static const int kMiterIndexCnt = 3 * 24; 425 static const int kMiterVertexCnt = 16; 426 static const int kNumMiterRectsInIndexBuffer = 256; 427 428 static const int kBevelIndexCnt = 48 + 36 + 24; 429 static const int kBevelVertexCnt = 24; 430 static const int kNumBevelRectsInIndexBuffer = 256; 431 432 static sk_sp<const GrGpuBuffer> GetIndexBuffer(GrResourceProvider*, bool miterStroke); 433 434 const SkMatrix& viewMatrix() const { return fViewMatrix; } 435 bool miterStroke() const { return fMiterStroke; } 436 437 CombineResult onCombineIfPossible(GrOp* t, const GrCaps&) override; 438 439 void generateAAStrokeRectGeometry(GrVertexWriter& vertices, 440 const SkPMColor4f& color, 441 bool wideColor, 442 const SkRect& devOutside, 443 const SkRect& devOutsideAssist, 444 const SkRect& devInside, 445 bool miterStroke, 446 bool degenerate, 447 bool tweakAlphaForCoverage) const; 448 449 // TODO support AA rotated stroke rects by copying around view matrices 450 struct RectInfo { 451 SkPMColor4f fColor; 452 SkRect fDevOutside; 453 SkRect fDevOutsideAssist; 454 SkRect fDevInside; 455 bool fDegenerate; 456 }; 457 458 Helper fHelper; 459 SkSTArray<1, RectInfo, true> fRects; 460 SkMatrix fViewMatrix; 461 bool fMiterStroke; 462 bool fWideColor; 463 464 typedef GrMeshDrawOp INHERITED; 465 }; 466 467 void AAStrokeRectOp::onPrepareDraws(Target* target) { 468 sk_sp<GrGeometryProcessor> gp(create_aa_stroke_rect_gp(target->caps().shaderCaps(), 469 fHelper.compatibleWithAlphaAsCoverage(), 470 this->viewMatrix(), 471 fHelper.usesLocalCoords(), 472 fWideColor)); 473 if (!gp) { 474 SkDebugf("Couldn't create GrGeometryProcessor\n"); 475 return; 476 } 477 478 int innerVertexNum = 4; 479 int outerVertexNum = this->miterStroke() ? 4 : 8; 480 int verticesPerInstance = (outerVertexNum + innerVertexNum) * 2; 481 int indicesPerInstance = this->miterStroke() ? kMiterIndexCnt : kBevelIndexCnt; 482 int instanceCount = fRects.count(); 483 484 sk_sp<const GrGpuBuffer> indexBuffer = 485 GetIndexBuffer(target->resourceProvider(), this->miterStroke()); 486 if (!indexBuffer) { 487 SkDebugf("Could not allocate indices\n"); 488 return; 489 } 490 PatternHelper helper(target, GrPrimitiveType::kTriangles, gp->vertexStride(), 491 std::move(indexBuffer), verticesPerInstance, indicesPerInstance, 492 instanceCount); 493 GrVertexWriter vertices{ helper.vertices() }; 494 if (!vertices.fPtr) { 495 SkDebugf("Could not allocate vertices\n"); 496 return; 497 } 498 499 for (int i = 0; i < instanceCount; i++) { 500 const RectInfo& info = fRects[i]; 501 this->generateAAStrokeRectGeometry(vertices, 502 info.fColor, 503 fWideColor, 504 info.fDevOutside, 505 info.fDevOutsideAssist, 506 info.fDevInside, 507 fMiterStroke, 508 info.fDegenerate, 509 fHelper.compatibleWithAlphaAsCoverage()); 510 } 511 helper.recordDraw(target, std::move(gp)); 512 } 513 514 void AAStrokeRectOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) { 515 fHelper.executeDrawsAndUploads(this, flushState, chainBounds); 516 } 517 518 sk_sp<const GrGpuBuffer> AAStrokeRectOp::GetIndexBuffer(GrResourceProvider* resourceProvider, 519 bool miterStroke) { 520 if (miterStroke) { 521 // clang-format off 522 static const uint16_t gMiterIndices[] = { 523 0 + 0, 1 + 0, 5 + 0, 5 + 0, 4 + 0, 0 + 0, 524 1 + 0, 2 + 0, 6 + 0, 6 + 0, 5 + 0, 1 + 0, 525 2 + 0, 3 + 0, 7 + 0, 7 + 0, 6 + 0, 2 + 0, 526 3 + 0, 0 + 0, 4 + 0, 4 + 0, 7 + 0, 3 + 0, 527 528 0 + 4, 1 + 4, 5 + 4, 5 + 4, 4 + 4, 0 + 4, 529 1 + 4, 2 + 4, 6 + 4, 6 + 4, 5 + 4, 1 + 4, 530 2 + 4, 3 + 4, 7 + 4, 7 + 4, 6 + 4, 2 + 4, 531 3 + 4, 0 + 4, 4 + 4, 4 + 4, 7 + 4, 3 + 4, 532 533 0 + 8, 1 + 8, 5 + 8, 5 + 8, 4 + 8, 0 + 8, 534 1 + 8, 2 + 8, 6 + 8, 6 + 8, 5 + 8, 1 + 8, 535 2 + 8, 3 + 8, 7 + 8, 7 + 8, 6 + 8, 2 + 8, 536 3 + 8, 0 + 8, 4 + 8, 4 + 8, 7 + 8, 3 + 8, 537 }; 538 // clang-format on 539 GR_STATIC_ASSERT(SK_ARRAY_COUNT(gMiterIndices) == kMiterIndexCnt); 540 GR_DEFINE_STATIC_UNIQUE_KEY(gMiterIndexBufferKey); 541 return resourceProvider->findOrCreatePatternedIndexBuffer( 542 gMiterIndices, kMiterIndexCnt, kNumMiterRectsInIndexBuffer, kMiterVertexCnt, 543 gMiterIndexBufferKey); 544 } else { 545 /** 546 * As in miter-stroke, index = a + b, and a is the current index, b is the shift 547 * from the first index. The index layout: 548 * outer AA line: 0~3, 4~7 549 * outer edge: 8~11, 12~15 550 * inner edge: 16~19 551 * inner AA line: 20~23 552 * Following comes a bevel-stroke rect and its indices: 553 * 554 * 4 7 555 * ********************************* 556 * * ______________________________ * 557 * * / 12 15 \ * 558 * * / \ * 559 * 0 * |8 16_____________________19 11 | * 3 560 * * | | | | * 561 * * | | **************** | | * 562 * * | | * 20 23 * | | * 563 * * | | * * | | * 564 * * | | * 21 22 * | | * 565 * * | | **************** | | * 566 * * | |____________________| | * 567 * 1 * |9 17 18 10| * 2 568 * * \ / * 569 * * \13 __________________________14/ * 570 * * * 571 * ********************************** 572 * 5 6 573 */ 574 // clang-format off 575 static const uint16_t gBevelIndices[] = { 576 // Draw outer AA, from outer AA line to outer edge, shift is 0. 577 0 + 0, 1 + 0, 9 + 0, 9 + 0, 8 + 0, 0 + 0, 578 1 + 0, 5 + 0, 13 + 0, 13 + 0, 9 + 0, 1 + 0, 579 5 + 0, 6 + 0, 14 + 0, 14 + 0, 13 + 0, 5 + 0, 580 6 + 0, 2 + 0, 10 + 0, 10 + 0, 14 + 0, 6 + 0, 581 2 + 0, 3 + 0, 11 + 0, 11 + 0, 10 + 0, 2 + 0, 582 3 + 0, 7 + 0, 15 + 0, 15 + 0, 11 + 0, 3 + 0, 583 7 + 0, 4 + 0, 12 + 0, 12 + 0, 15 + 0, 7 + 0, 584 4 + 0, 0 + 0, 8 + 0, 8 + 0, 12 + 0, 4 + 0, 585 586 // Draw the stroke, from outer edge to inner edge, shift is 8. 587 0 + 8, 1 + 8, 9 + 8, 9 + 8, 8 + 8, 0 + 8, 588 1 + 8, 5 + 8, 9 + 8, 589 5 + 8, 6 + 8, 10 + 8, 10 + 8, 9 + 8, 5 + 8, 590 6 + 8, 2 + 8, 10 + 8, 591 2 + 8, 3 + 8, 11 + 8, 11 + 8, 10 + 8, 2 + 8, 592 3 + 8, 7 + 8, 11 + 8, 593 7 + 8, 4 + 8, 8 + 8, 8 + 8, 11 + 8, 7 + 8, 594 4 + 8, 0 + 8, 8 + 8, 595 596 // Draw the inner AA, from inner edge to inner AA line, shift is 16. 597 0 + 16, 1 + 16, 5 + 16, 5 + 16, 4 + 16, 0 + 16, 598 1 + 16, 2 + 16, 6 + 16, 6 + 16, 5 + 16, 1 + 16, 599 2 + 16, 3 + 16, 7 + 16, 7 + 16, 6 + 16, 2 + 16, 600 3 + 16, 0 + 16, 4 + 16, 4 + 16, 7 + 16, 3 + 16, 601 }; 602 // clang-format on 603 GR_STATIC_ASSERT(SK_ARRAY_COUNT(gBevelIndices) == kBevelIndexCnt); 604 605 GR_DEFINE_STATIC_UNIQUE_KEY(gBevelIndexBufferKey); 606 return resourceProvider->findOrCreatePatternedIndexBuffer( 607 gBevelIndices, kBevelIndexCnt, kNumBevelRectsInIndexBuffer, kBevelVertexCnt, 608 gBevelIndexBufferKey); 609 } 610 } 611 612 GrOp::CombineResult AAStrokeRectOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) { 613 AAStrokeRectOp* that = t->cast<AAStrokeRectOp>(); 614 615 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { 616 return CombineResult::kCannotCombine; 617 } 618 619 // TODO combine across miterstroke changes 620 if (this->miterStroke() != that->miterStroke()) { 621 return CombineResult::kCannotCombine; 622 } 623 624 // We apply the viewmatrix to the rect points on the cpu. However, if the pipeline uses 625 // local coords then we won't be able to combine. TODO: Upload local coords as an attribute. 626 if (fHelper.usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) { 627 return CombineResult::kCannotCombine; 628 } 629 630 fRects.push_back_n(that->fRects.count(), that->fRects.begin()); 631 fWideColor |= that->fWideColor; 632 return CombineResult::kMerged; 633 } 634 635 static void setup_scale(int* scale, SkScalar inset) { 636 if (inset < SK_ScalarHalf) { 637 *scale = SkScalarFloorToInt(512.0f * inset / (inset + SK_ScalarHalf)); 638 SkASSERT(*scale >= 0 && *scale <= 255); 639 } else { 640 *scale = 0xff; 641 } 642 } 643 644 void AAStrokeRectOp::generateAAStrokeRectGeometry(GrVertexWriter& vertices, 645 const SkPMColor4f& color, 646 bool wideColor, 647 const SkRect& devOutside, 648 const SkRect& devOutsideAssist, 649 const SkRect& devInside, 650 bool miterStroke, 651 bool degenerate, 652 bool tweakAlphaForCoverage) const { 653 // We create vertices for four nested rectangles. There are two ramps from 0 to full 654 // coverage, one on the exterior of the stroke and the other on the interior. 655 656 // TODO: this only really works if the X & Y margins are the same all around 657 // the rect (or if they are all >= 1.0). 658 SkScalar inset; 659 if (!degenerate) { 660 inset = SkMinScalar(SK_Scalar1, devOutside.fRight - devInside.fRight); 661 inset = SkMinScalar(inset, devInside.fLeft - devOutside.fLeft); 662 inset = SkMinScalar(inset, devInside.fTop - devOutside.fTop); 663 if (miterStroke) { 664 inset = SK_ScalarHalf * SkMinScalar(inset, devOutside.fBottom - devInside.fBottom); 665 } else { 666 inset = SK_ScalarHalf * 667 SkMinScalar(inset, devOutsideAssist.fBottom - devInside.fBottom); 668 } 669 SkASSERT(inset >= 0); 670 } else { 671 // TODO use real devRect here 672 inset = SkMinScalar(devOutside.width(), SK_Scalar1); 673 inset = SK_ScalarHalf * 674 SkMinScalar(inset, SkTMax(devOutside.height(), devOutsideAssist.height())); 675 } 676 677 auto inset_fan = [](const SkRect& r, SkScalar dx, SkScalar dy) { 678 return GrVertexWriter::TriFanFromRect(r.makeInset(dx, dy)); 679 }; 680 681 auto maybe_coverage = [tweakAlphaForCoverage](float coverage) { 682 return GrVertexWriter::If(!tweakAlphaForCoverage, coverage); 683 }; 684 685 GrVertexColor outerColor(tweakAlphaForCoverage ? SK_PMColor4fTRANSPARENT : color, wideColor); 686 687 // Outermost rect 688 vertices.writeQuad(inset_fan(devOutside, -SK_ScalarHalf, -SK_ScalarHalf), 689 outerColor, 690 maybe_coverage(0.0f)); 691 692 if (!miterStroke) { 693 // Second outermost 694 vertices.writeQuad(inset_fan(devOutsideAssist, -SK_ScalarHalf, -SK_ScalarHalf), 695 outerColor, 696 maybe_coverage(0.0f)); 697 } 698 699 // scale is the coverage for the the inner two rects. 700 int scale; 701 setup_scale(&scale, inset); 702 703 float innerCoverage = GrNormalizeByteToFloat(scale); 704 SkPMColor4f scaledColor = color * innerCoverage; 705 GrVertexColor innerColor(tweakAlphaForCoverage ? scaledColor : color, wideColor); 706 707 // Inner rect 708 vertices.writeQuad(inset_fan(devOutside, inset, inset), 709 innerColor, 710 maybe_coverage(innerCoverage)); 711 712 if (!miterStroke) { 713 // Second inner 714 vertices.writeQuad(inset_fan(devOutsideAssist, inset, inset), 715 innerColor, 716 maybe_coverage(innerCoverage)); 717 } 718 719 if (!degenerate) { 720 vertices.writeQuad(inset_fan(devInside, -inset, -inset), 721 innerColor, 722 maybe_coverage(innerCoverage)); 723 724 // The innermost rect has 0 coverage... 725 vertices.writeQuad(inset_fan(devInside, SK_ScalarHalf, SK_ScalarHalf), 726 GrVertexColor(SK_PMColor4fTRANSPARENT, wideColor), 727 maybe_coverage(0.0f)); 728 } else { 729 // When the interior rect has become degenerate we smoosh to a single point 730 SkASSERT(devInside.fLeft == devInside.fRight && devInside.fTop == devInside.fBottom); 731 732 vertices.writeQuad(GrVertexWriter::TriFanFromRect(devInside), 733 innerColor, 734 maybe_coverage(innerCoverage)); 735 736 // ... unless we are degenerate, in which case we must apply the scaled coverage 737 vertices.writeQuad(GrVertexWriter::TriFanFromRect(devInside), 738 innerColor, 739 maybe_coverage(innerCoverage)); 740 } 741 } 742 743 } // anonymous namespace 744 745 namespace GrStrokeRectOp { 746 747 std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context, 748 GrPaint&& paint, 749 GrAAType aaType, 750 const SkMatrix& viewMatrix, 751 const SkRect& rect, 752 const SkStrokeRec& stroke) { 753 if (aaType == GrAAType::kCoverage) { 754 // The AA op only supports axis-aligned rectangles 755 if (!viewMatrix.rectStaysRect()) { 756 return nullptr; 757 } 758 return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke); 759 } else { 760 return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke, aaType); 761 } 762 } 763 764 std::unique_ptr<GrDrawOp> MakeNested(GrRecordingContext* context, 765 GrPaint&& paint, 766 const SkMatrix& viewMatrix, 767 const SkRect rects[2]) { 768 SkASSERT(viewMatrix.rectStaysRect()); 769 SkASSERT(!rects[0].isEmpty() && !rects[1].isEmpty()); 770 771 SkRect devOutside, devInside; 772 viewMatrix.mapRect(&devOutside, rects[0]); 773 viewMatrix.mapRect(&devInside, rects[1]); 774 if (devInside.isEmpty()) { 775 if (devOutside.isEmpty()) { 776 return nullptr; 777 } 778 return GrFillRectOp::Make(context, std::move(paint), GrAAType::kCoverage, viewMatrix, 779 rects[0]); 780 } 781 782 return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, devOutside, devInside); 783 } 784 785 } // namespace GrStrokeRectOp 786 787 #if GR_TEST_UTILS 788 789 #include "GrDrawOpTest.h" 790 791 GR_DRAW_OP_TEST_DEFINE(NonAAStrokeRectOp) { 792 SkMatrix viewMatrix = GrTest::TestMatrix(random); 793 SkRect rect = GrTest::TestRect(random); 794 SkScalar strokeWidth = random->nextBool() ? 0.0f : 2.0f; 795 SkPaint strokePaint; 796 strokePaint.setStrokeWidth(strokeWidth); 797 strokePaint.setStyle(SkPaint::kStroke_Style); 798 strokePaint.setStrokeJoin(SkPaint::kMiter_Join); 799 SkStrokeRec strokeRec(strokePaint); 800 GrAAType aaType = GrAAType::kNone; 801 if (fsaaType == GrFSAAType::kUnifiedMSAA) { 802 aaType = random->nextBool() ? GrAAType::kMSAA : GrAAType::kNone; 803 } 804 return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, strokeRec, aaType); 805 } 806 807 GR_DRAW_OP_TEST_DEFINE(AAStrokeRectOp) { 808 bool miterStroke = random->nextBool(); 809 810 // Create either a empty rect or a non-empty rect. 811 SkRect rect = 812 random->nextBool() ? SkRect::MakeXYWH(10, 10, 50, 40) : SkRect::MakeXYWH(6, 7, 0, 0); 813 SkScalar minDim = SkMinScalar(rect.width(), rect.height()); 814 SkScalar strokeWidth = random->nextUScalar1() * minDim; 815 816 SkStrokeRec rec(SkStrokeRec::kFill_InitStyle); 817 rec.setStrokeStyle(strokeWidth); 818 rec.setStrokeParams(SkPaint::kButt_Cap, 819 miterStroke ? SkPaint::kMiter_Join : SkPaint::kBevel_Join, 1.f); 820 SkMatrix matrix = GrTest::TestMatrixRectStaysRect(random); 821 return AAStrokeRectOp::Make(context, std::move(paint), matrix, rect, rec); 822 } 823 824 #endif 825