1/* 2 * Copyright 2014 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/DashOp.h" 9 10#include "include/gpu/GrRecordingContext.h" 11#include "src/core/SkMatrixPriv.h" 12#include "src/core/SkPointPriv.h" 13#include "src/gpu/BufferWriter.h" 14#include "src/gpu/GrAppliedClip.h" 15#include "src/gpu/GrCaps.h" 16#include "src/gpu/GrDefaultGeoProcFactory.h" 17#include "src/gpu/GrGeometryProcessor.h" 18#include "src/gpu/GrMemoryPool.h" 19#include "src/gpu/GrOpFlushState.h" 20#include "src/gpu/GrProcessor.h" 21#include "src/gpu/GrProgramInfo.h" 22#include "src/gpu/GrRecordingContextPriv.h" 23#include "src/gpu/GrStyle.h" 24#include "src/gpu/SkGr.h" 25#include "src/gpu/geometry/GrQuad.h" 26#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" 27#include "src/gpu/glsl/GrGLSLProgramDataManager.h" 28#include "src/gpu/glsl/GrGLSLUniformHandler.h" 29#include "src/gpu/glsl/GrGLSLVarying.h" 30#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" 31#include "src/gpu/ops/GrMeshDrawOp.h" 32#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" 33 34using AAMode = skgpu::v1::DashOp::AAMode; 35 36#if GR_TEST_UTILS 37static const int kAAModeCnt = static_cast<int>(skgpu::v1::DashOp::AAMode::kCoverageWithMSAA) + 1; 38#endif 39 40namespace skgpu::v1::DashOp { 41 42namespace { 43 44void calc_dash_scaling(SkScalar* parallelScale, SkScalar* perpScale, 45 const SkMatrix& viewMatrix, const SkPoint pts[2]) { 46 SkVector vecSrc = pts[1] - pts[0]; 47 if (pts[1] == pts[0]) { 48 vecSrc.set(1.0, 0.0); 49 } 50 SkScalar magSrc = vecSrc.length(); 51 SkScalar invSrc = magSrc ? SkScalarInvert(magSrc) : 0; 52 vecSrc.scale(invSrc); 53 54 SkVector vecSrcPerp; 55 SkPointPriv::RotateCW(vecSrc, &vecSrcPerp); 56 viewMatrix.mapVectors(&vecSrc, 1); 57 viewMatrix.mapVectors(&vecSrcPerp, 1); 58 59 // parallelScale tells how much to scale along the line parallel to the dash line 60 // perpScale tells how much to scale in the direction perpendicular to the dash line 61 *parallelScale = vecSrc.length(); 62 *perpScale = vecSrcPerp.length(); 63} 64 65// calculates the rotation needed to aligned pts to the x axis with pts[0] < pts[1] 66// Stores the rotation matrix in rotMatrix, and the mapped points in ptsRot 67void align_to_x_axis(const SkPoint pts[2], SkMatrix* rotMatrix, SkPoint ptsRot[2] = nullptr) { 68 SkVector vec = pts[1] - pts[0]; 69 if (pts[1] == pts[0]) { 70 vec.set(1.0, 0.0); 71 } 72 SkScalar mag = vec.length(); 73 SkScalar inv = mag ? SkScalarInvert(mag) : 0; 74 75 vec.scale(inv); 76 rotMatrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY); 77 if (ptsRot) { 78 rotMatrix->mapPoints(ptsRot, pts, 2); 79 // correction for numerical issues if map doesn't make ptsRot exactly horizontal 80 ptsRot[1].fY = pts[0].fY; 81 } 82} 83 84// Assumes phase < sum of all intervals 85SkScalar calc_start_adjustment(const SkScalar intervals[2], SkScalar phase) { 86 SkASSERT(phase < intervals[0] + intervals[1]); 87 if (phase >= intervals[0] && phase != 0) { 88 SkScalar srcIntervalLen = intervals[0] + intervals[1]; 89 return srcIntervalLen - phase; 90 } 91 return 0; 92} 93 94SkScalar calc_end_adjustment(const SkScalar intervals[2], const SkPoint pts[2], 95 SkScalar phase, SkScalar* endingInt) { 96 if (pts[1].fX <= pts[0].fX) { 97 return 0; 98 } 99 SkScalar srcIntervalLen = intervals[0] + intervals[1]; 100 SkScalar totalLen = pts[1].fX - pts[0].fX; 101 SkScalar temp = totalLen / srcIntervalLen; 102 SkScalar numFullIntervals = SkScalarFloorToScalar(temp); 103 *endingInt = totalLen - numFullIntervals * srcIntervalLen + phase; 104 temp = *endingInt / srcIntervalLen; 105 *endingInt = *endingInt - SkScalarFloorToScalar(temp) * srcIntervalLen; 106 if (0 == *endingInt) { 107 *endingInt = srcIntervalLen; 108 } 109 if (*endingInt > intervals[0]) { 110 return *endingInt - intervals[0]; 111 } 112 return 0; 113} 114 115enum DashCap { 116 kRound_DashCap, 117 kNonRound_DashCap, 118}; 119 120void setup_dashed_rect(const SkRect& rect, 121 VertexWriter& vertices, 122 const SkMatrix& matrix, 123 SkScalar offset, 124 SkScalar bloatX, 125 SkScalar len, 126 SkScalar startInterval, 127 SkScalar endInterval, 128 SkScalar strokeWidth, 129 SkScalar perpScale, 130 DashCap cap) { 131 SkScalar intervalLength = startInterval + endInterval; 132 // 'dashRect' gets interpolated over the rendered 'rect'. For y we want the perpendicular signed 133 // distance from the stroke center line in device space. 'perpScale' is the scale factor applied 134 // to the y dimension of 'rect' isolated from 'matrix'. 135 SkScalar halfDevRectHeight = rect.height() * perpScale / 2.f; 136 SkRect dashRect = { offset - bloatX, -halfDevRectHeight, 137 offset + len + bloatX, halfDevRectHeight }; 138 139 if (kRound_DashCap == cap) { 140 SkScalar radius = SkScalarHalf(strokeWidth) - 0.5f; 141 SkScalar centerX = SkScalarHalf(endInterval); 142 143 vertices.writeQuad(GrQuad::MakeFromRect(rect, matrix), 144 VertexWriter::TriStripFromRect(dashRect), 145 intervalLength, 146 radius, 147 centerX); 148 } else { 149 SkASSERT(kNonRound_DashCap == cap); 150 SkScalar halfOffLen = SkScalarHalf(endInterval); 151 SkScalar halfStroke = SkScalarHalf(strokeWidth); 152 SkRect rectParam; 153 rectParam.setLTRB(halfOffLen + 0.5f, -halfStroke + 0.5f, 154 halfOffLen + startInterval - 0.5f, halfStroke - 0.5f); 155 156 vertices.writeQuad(GrQuad::MakeFromRect(rect, matrix), 157 VertexWriter::TriStripFromRect(dashRect), 158 intervalLength, 159 rectParam); 160 } 161} 162 163/** 164 * An GrGeometryProcessor that renders a dashed line. 165 * This GrGeometryProcessor is meant for dashed lines that only have a single on/off interval pair. 166 * Bounding geometry is rendered and the effect computes coverage based on the fragment's 167 * position relative to the dashed line. 168 */ 169GrGeometryProcessor* make_dash_gp(SkArenaAlloc* arena, 170 const SkPMColor4f&, 171 AAMode aaMode, 172 DashCap cap, 173 const SkMatrix& localMatrix, 174 bool usesLocalCoords); 175 176class DashOpImpl final : public GrMeshDrawOp { 177public: 178 DEFINE_OP_CLASS_ID 179 180 struct LineData { 181 SkMatrix fViewMatrix; 182 SkMatrix fSrcRotInv; 183 SkPoint fPtsRot[2]; 184 SkScalar fSrcStrokeWidth; 185 SkScalar fPhase; 186 SkScalar fIntervals[2]; 187 SkScalar fParallelScale; 188 SkScalar fPerpendicularScale; 189 }; 190 191 static GrOp::Owner Make(GrRecordingContext* context, 192 GrPaint&& paint, 193 const LineData& geometry, 194 SkPaint::Cap cap, 195 AAMode aaMode, bool fullDash, 196 const GrUserStencilSettings* stencilSettings) { 197 return GrOp::Make<DashOpImpl>(context, std::move(paint), geometry, cap, 198 aaMode, fullDash, stencilSettings); 199 } 200 201 const char* name() const override { return "DashOp"; } 202 203 void visitProxies(const GrVisitProxyFunc& func) const override { 204 if (fProgramInfo) { 205 fProgramInfo->visitFPProxies(func); 206 } else { 207 fProcessorSet.visitProxies(func); 208 } 209 } 210 211 FixedFunctionFlags fixedFunctionFlags() const override { 212 FixedFunctionFlags flags = FixedFunctionFlags::kNone; 213 if (AAMode::kCoverageWithMSAA == fAAMode) { 214 flags |= FixedFunctionFlags::kUsesHWAA; 215 } 216 if (fStencilSettings != &GrUserStencilSettings::kUnused) { 217 flags |= FixedFunctionFlags::kUsesStencil; 218 } 219 return flags; 220 } 221 222 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip, 223 GrClampType clampType) override { 224 GrProcessorAnalysisCoverage coverage = GrProcessorAnalysisCoverage::kSingleChannel; 225 auto analysis = fProcessorSet.finalize(fColor, coverage, clip, fStencilSettings, caps, 226 clampType, &fColor); 227 fUsesLocalCoords = analysis.usesLocalCoords(); 228 return analysis; 229 } 230 231private: 232 friend class GrOp; // for ctor 233 234 DashOpImpl(GrPaint&& paint, const LineData& geometry, SkPaint::Cap cap, AAMode aaMode, 235 bool fullDash, const GrUserStencilSettings* stencilSettings) 236 : INHERITED(ClassID()) 237 , fColor(paint.getColor4f()) 238 , fFullDash(fullDash) 239 , fCap(cap) 240 , fAAMode(aaMode) 241 , fProcessorSet(std::move(paint)) 242 , fStencilSettings(stencilSettings) { 243 fLines.push_back(geometry); 244 245 // compute bounds 246 SkScalar halfStrokeWidth = 0.5f * geometry.fSrcStrokeWidth; 247 SkScalar xBloat = SkPaint::kButt_Cap == cap ? 0 : halfStrokeWidth; 248 SkRect bounds; 249 bounds.set(geometry.fPtsRot[0], geometry.fPtsRot[1]); 250 bounds.outset(xBloat, halfStrokeWidth); 251 252 // Note, we actually create the combined matrix here, and save the work 253 SkMatrix& combinedMatrix = fLines[0].fSrcRotInv; 254 combinedMatrix.postConcat(geometry.fViewMatrix); 255 256 IsHairline zeroArea = geometry.fSrcStrokeWidth ? IsHairline::kNo : IsHairline::kYes; 257 HasAABloat aaBloat = (aaMode == AAMode::kNone) ? HasAABloat::kNo : HasAABloat::kYes; 258 this->setTransformedBounds(bounds, combinedMatrix, aaBloat, zeroArea); 259 } 260 261 struct DashDraw { 262 DashDraw(const LineData& geo) { 263 memcpy(fPtsRot, geo.fPtsRot, sizeof(geo.fPtsRot)); 264 memcpy(fIntervals, geo.fIntervals, sizeof(geo.fIntervals)); 265 fPhase = geo.fPhase; 266 } 267 SkPoint fPtsRot[2]; 268 SkScalar fIntervals[2]; 269 SkScalar fPhase; 270 SkScalar fStartOffset; 271 SkScalar fStrokeWidth; 272 SkScalar fLineLength; 273 SkScalar fDevBloatX; 274 SkScalar fPerpendicularScale; 275 bool fLineDone; 276 bool fHasStartRect; 277 bool fHasEndRect; 278 }; 279 280 GrProgramInfo* programInfo() override { return fProgramInfo; } 281 282 void onCreateProgramInfo(const GrCaps* caps, 283 SkArenaAlloc* arena, 284 const GrSurfaceProxyView& writeView, 285 bool usesMSAASurface, 286 GrAppliedClip&& appliedClip, 287 const GrDstProxyView& dstProxyView, 288 GrXferBarrierFlags renderPassXferBarriers, 289 GrLoadOp colorLoadOp) override { 290 291 DashCap capType = (this->cap() == SkPaint::kRound_Cap) ? kRound_DashCap : kNonRound_DashCap; 292 293 GrGeometryProcessor* gp; 294 if (this->fullDash()) { 295 gp = make_dash_gp(arena, this->color(), this->aaMode(), capType, 296 this->viewMatrix(), fUsesLocalCoords); 297 } else { 298 // Set up the vertex data for the line and start/end dashes 299 using namespace GrDefaultGeoProcFactory; 300 Color color(this->color()); 301 LocalCoords::Type localCoordsType = 302 fUsesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type; 303 gp = MakeForDeviceSpace(arena, 304 color, 305 Coverage::kSolid_Type, 306 localCoordsType, 307 this->viewMatrix()); 308 } 309 310 if (!gp) { 311 SkDebugf("Could not create GrGeometryProcessor\n"); 312 return; 313 } 314 315 fProgramInfo = GrSimpleMeshDrawOpHelper::CreateProgramInfo(caps, 316 arena, 317 writeView, 318 usesMSAASurface, 319 std::move(appliedClip), 320 dstProxyView, 321 gp, 322 std::move(fProcessorSet), 323 GrPrimitiveType::kTriangles, 324 renderPassXferBarriers, 325 colorLoadOp, 326 GrPipeline::InputFlags::kNone, 327 fStencilSettings); 328 } 329 330 void onPrepareDraws(GrMeshDrawTarget* target) override { 331 int instanceCount = fLines.count(); 332 SkPaint::Cap cap = this->cap(); 333 DashCap capType = (SkPaint::kRound_Cap == cap) ? kRound_DashCap : kNonRound_DashCap; 334 335 if (!fProgramInfo) { 336 this->createProgramInfo(target); 337 if (!fProgramInfo) { 338 return; 339 } 340 } 341 342 // useAA here means Edge AA or MSAA 343 bool useAA = this->aaMode() != AAMode::kNone; 344 bool fullDash = this->fullDash(); 345 346 // We do two passes over all of the dashes. First we setup the start, end, and bounds, 347 // rectangles. We preserve all of this work in the rects / draws arrays below. Then we 348 // iterate again over these decomposed dashes to generate vertices 349 static const int kNumStackDashes = 128; 350 SkSTArray<kNumStackDashes, SkRect, true> rects; 351 SkSTArray<kNumStackDashes, DashDraw, true> draws; 352 353 int totalRectCount = 0; 354 int rectOffset = 0; 355 rects.push_back_n(3 * instanceCount); 356 for (int i = 0; i < instanceCount; i++) { 357 const LineData& args = fLines[i]; 358 359 DashDraw& draw = draws.push_back(args); 360 361 bool hasCap = SkPaint::kButt_Cap != cap; 362 363 SkScalar halfSrcStroke = args.fSrcStrokeWidth * 0.5f; 364 if (halfSrcStroke == 0.0f || this->aaMode() != AAMode::kCoverageWithMSAA) { 365 // In the non-MSAA case, we always want to at least stroke out half a pixel on each 366 // side in device space. 0.5f / fPerpendicularScale gives us this min in src space. 367 // This is also necessary when the stroke width is zero, to allow hairlines to draw. 368 halfSrcStroke = std::max(halfSrcStroke, 0.5f / args.fPerpendicularScale); 369 } 370 371 SkScalar strokeAdj = hasCap ? halfSrcStroke : 0.0f; 372 SkScalar startAdj = 0; 373 374 bool lineDone = false; 375 376 // Too simplify the algorithm, we always push back rects for start and end rect. 377 // Otherwise we'd have to track start / end rects for each individual geometry 378 SkRect& bounds = rects[rectOffset++]; 379 SkRect& startRect = rects[rectOffset++]; 380 SkRect& endRect = rects[rectOffset++]; 381 382 bool hasStartRect = false; 383 // If we are using AA, check to see if we are drawing a partial dash at the start. If so 384 // draw it separately here and adjust our start point accordingly 385 if (useAA) { 386 if (draw.fPhase > 0 && draw.fPhase < draw.fIntervals[0]) { 387 SkPoint startPts[2]; 388 startPts[0] = draw.fPtsRot[0]; 389 startPts[1].fY = startPts[0].fY; 390 startPts[1].fX = std::min(startPts[0].fX + draw.fIntervals[0] - draw.fPhase, 391 draw.fPtsRot[1].fX); 392 startRect.setBounds(startPts, 2); 393 startRect.outset(strokeAdj, halfSrcStroke); 394 395 hasStartRect = true; 396 startAdj = draw.fIntervals[0] + draw.fIntervals[1] - draw.fPhase; 397 } 398 } 399 400 // adjustments for start and end of bounding rect so we only draw dash intervals 401 // contained in the original line segment. 402 startAdj += calc_start_adjustment(draw.fIntervals, draw.fPhase); 403 if (startAdj != 0) { 404 draw.fPtsRot[0].fX += startAdj; 405 draw.fPhase = 0; 406 } 407 SkScalar endingInterval = 0; 408 SkScalar endAdj = calc_end_adjustment(draw.fIntervals, draw.fPtsRot, draw.fPhase, 409 &endingInterval); 410 draw.fPtsRot[1].fX -= endAdj; 411 if (draw.fPtsRot[0].fX >= draw.fPtsRot[1].fX) { 412 lineDone = true; 413 } 414 415 bool hasEndRect = false; 416 // If we are using AA, check to see if we are drawing a partial dash at then end. If so 417 // draw it separately here and adjust our end point accordingly 418 if (useAA && !lineDone) { 419 // If we adjusted the end then we will not be drawing a partial dash at the end. 420 // If we didn't adjust the end point then we just need to make sure the ending 421 // dash isn't a full dash 422 if (0 == endAdj && endingInterval != draw.fIntervals[0]) { 423 SkPoint endPts[2]; 424 endPts[1] = draw.fPtsRot[1]; 425 endPts[0].fY = endPts[1].fY; 426 endPts[0].fX = endPts[1].fX - endingInterval; 427 428 endRect.setBounds(endPts, 2); 429 endRect.outset(strokeAdj, halfSrcStroke); 430 431 hasEndRect = true; 432 endAdj = endingInterval + draw.fIntervals[1]; 433 434 draw.fPtsRot[1].fX -= endAdj; 435 if (draw.fPtsRot[0].fX >= draw.fPtsRot[1].fX) { 436 lineDone = true; 437 } 438 } 439 } 440 441 if (draw.fPtsRot[0].fX == draw.fPtsRot[1].fX && 442 (0 != endAdj || 0 == startAdj) && 443 hasCap) { 444 // At this point the fPtsRot[0]/[1] represent the start and end of the inner rect of 445 // dashes that we want to draw. The only way they can be equal is if the on interval 446 // is zero (or an edge case if the end of line ends at a full off interval, but this 447 // is handled as well). Thus if the on interval is zero then we need to draw a cap 448 // at this position if the stroke has caps. The spec says we only draw this point if 449 // point lies between [start of line, end of line). Thus we check if we are at the 450 // end (but not the start), and if so we don't draw the cap. 451 lineDone = false; 452 } 453 454 if (startAdj != 0) { 455 draw.fPhase = 0; 456 } 457 458 // Change the dashing info from src space into device space 459 SkScalar* devIntervals = draw.fIntervals; 460 devIntervals[0] = draw.fIntervals[0] * args.fParallelScale; 461 devIntervals[1] = draw.fIntervals[1] * args.fParallelScale; 462 SkScalar devPhase = draw.fPhase * args.fParallelScale; 463 SkScalar strokeWidth = args.fSrcStrokeWidth * args.fPerpendicularScale; 464 465 if ((strokeWidth < 1.f && !useAA) || 0.f == strokeWidth) { 466 strokeWidth = 1.f; 467 } 468 469 SkScalar halfDevStroke = strokeWidth * 0.5f; 470 471 if (SkPaint::kSquare_Cap == cap) { 472 // add cap to on interval and remove from off interval 473 devIntervals[0] += strokeWidth; 474 devIntervals[1] -= strokeWidth; 475 } 476 SkScalar startOffset = devIntervals[1] * 0.5f + devPhase; 477 478 SkScalar devBloatX = 0.0f; 479 SkScalar devBloatY = 0.0f; 480 switch (this->aaMode()) { 481 case AAMode::kNone: 482 break; 483 case AAMode::kCoverage: 484 // For EdgeAA, we bloat in X & Y for both square and round caps. 485 devBloatX = 0.5f; 486 devBloatY = 0.5f; 487 break; 488 case AAMode::kCoverageWithMSAA: 489 // For MSAA, we only bloat in Y for round caps. 490 devBloatY = (cap == SkPaint::kRound_Cap) ? 0.5f : 0.0f; 491 break; 492 } 493 494 SkScalar bloatX = devBloatX / args.fParallelScale; 495 SkScalar bloatY = devBloatY / args.fPerpendicularScale; 496 497 if (devIntervals[1] <= 0.f && useAA) { 498 // Case when we end up drawing a solid AA rect 499 // Reset the start rect to draw this single solid rect 500 // but it requires to upload a new intervals uniform so we can mimic 501 // one giant dash 502 draw.fPtsRot[0].fX -= hasStartRect ? startAdj : 0; 503 draw.fPtsRot[1].fX += hasEndRect ? endAdj : 0; 504 startRect.setBounds(draw.fPtsRot, 2); 505 startRect.outset(strokeAdj, halfSrcStroke); 506 hasStartRect = true; 507 hasEndRect = false; 508 lineDone = true; 509 510 SkPoint devicePts[2]; 511 args.fSrcRotInv.mapPoints(devicePts, draw.fPtsRot, 2); 512 SkScalar lineLength = SkPoint::Distance(devicePts[0], devicePts[1]); 513 if (hasCap) { 514 lineLength += 2.f * halfDevStroke; 515 } 516 devIntervals[0] = lineLength; 517 } 518 519 totalRectCount += !lineDone ? 1 : 0; 520 totalRectCount += hasStartRect ? 1 : 0; 521 totalRectCount += hasEndRect ? 1 : 0; 522 523 if (SkPaint::kRound_Cap == cap && 0 != args.fSrcStrokeWidth) { 524 // need to adjust this for round caps to correctly set the dashPos attrib on 525 // vertices 526 startOffset -= halfDevStroke; 527 } 528 529 if (!lineDone) { 530 SkPoint devicePts[2]; 531 args.fSrcRotInv.mapPoints(devicePts, draw.fPtsRot, 2); 532 draw.fLineLength = SkPoint::Distance(devicePts[0], devicePts[1]); 533 if (hasCap) { 534 draw.fLineLength += 2.f * halfDevStroke; 535 } 536 537 bounds.setLTRB(draw.fPtsRot[0].fX, draw.fPtsRot[0].fY, 538 draw.fPtsRot[1].fX, draw.fPtsRot[1].fY); 539 bounds.outset(bloatX + strokeAdj, bloatY + halfSrcStroke); 540 } 541 542 if (hasStartRect) { 543 SkASSERT(useAA); // so that we know bloatX and bloatY have been set 544 startRect.outset(bloatX, bloatY); 545 } 546 547 if (hasEndRect) { 548 SkASSERT(useAA); // so that we know bloatX and bloatY have been set 549 endRect.outset(bloatX, bloatY); 550 } 551 552 draw.fStartOffset = startOffset; 553 draw.fDevBloatX = devBloatX; 554 draw.fPerpendicularScale = args.fPerpendicularScale; 555 draw.fStrokeWidth = strokeWidth; 556 draw.fHasStartRect = hasStartRect; 557 draw.fLineDone = lineDone; 558 draw.fHasEndRect = hasEndRect; 559 } 560 561 if (!totalRectCount) { 562 return; 563 } 564 565 QuadHelper helper(target, fProgramInfo->geomProc().vertexStride(), totalRectCount); 566 VertexWriter vertices{ helper.vertices() }; 567 if (!vertices) { 568 return; 569 } 570 571 int rectIndex = 0; 572 for (int i = 0; i < instanceCount; i++) { 573 const LineData& geom = fLines[i]; 574 575 if (!draws[i].fLineDone) { 576 if (fullDash) { 577 setup_dashed_rect(rects[rectIndex], vertices, geom.fSrcRotInv, 578 draws[i].fStartOffset, draws[i].fDevBloatX, 579 draws[i].fLineLength, draws[i].fIntervals[0], 580 draws[i].fIntervals[1], draws[i].fStrokeWidth, 581 draws[i].fPerpendicularScale, 582 capType); 583 } else { 584 vertices.writeQuad(GrQuad::MakeFromRect(rects[rectIndex], geom.fSrcRotInv)); 585 } 586 } 587 rectIndex++; 588 589 if (draws[i].fHasStartRect) { 590 if (fullDash) { 591 setup_dashed_rect(rects[rectIndex], vertices, geom.fSrcRotInv, 592 draws[i].fStartOffset, draws[i].fDevBloatX, 593 draws[i].fIntervals[0], draws[i].fIntervals[0], 594 draws[i].fIntervals[1], draws[i].fStrokeWidth, 595 draws[i].fPerpendicularScale, capType); 596 } else { 597 vertices.writeQuad(GrQuad::MakeFromRect(rects[rectIndex], geom.fSrcRotInv)); 598 } 599 } 600 rectIndex++; 601 602 if (draws[i].fHasEndRect) { 603 if (fullDash) { 604 setup_dashed_rect(rects[rectIndex], vertices, geom.fSrcRotInv, 605 draws[i].fStartOffset, draws[i].fDevBloatX, 606 draws[i].fIntervals[0], draws[i].fIntervals[0], 607 draws[i].fIntervals[1], draws[i].fStrokeWidth, 608 draws[i].fPerpendicularScale, capType); 609 } else { 610 vertices.writeQuad(GrQuad::MakeFromRect(rects[rectIndex], geom.fSrcRotInv)); 611 } 612 } 613 rectIndex++; 614 } 615 616 fMesh = helper.mesh(); 617 } 618 619 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { 620 if (!fProgramInfo || !fMesh) { 621 return; 622 } 623 624 flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds); 625 flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline()); 626 flushState->drawMesh(*fMesh); 627 } 628 629 CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override { 630 auto that = t->cast<DashOpImpl>(); 631 if (fProcessorSet != that->fProcessorSet) { 632 return CombineResult::kCannotCombine; 633 } 634 635 if (this->aaMode() != that->aaMode()) { 636 return CombineResult::kCannotCombine; 637 } 638 639 if (this->fullDash() != that->fullDash()) { 640 return CombineResult::kCannotCombine; 641 } 642 643 if (this->cap() != that->cap()) { 644 return CombineResult::kCannotCombine; 645 } 646 647 // TODO vertex color 648 if (this->color() != that->color()) { 649 return CombineResult::kCannotCombine; 650 } 651 652 if (fUsesLocalCoords && !SkMatrixPriv::CheapEqual(this->viewMatrix(), that->viewMatrix())) { 653 return CombineResult::kCannotCombine; 654 } 655 656 fLines.push_back_n(that->fLines.count(), that->fLines.begin()); 657 return CombineResult::kMerged; 658 } 659 660#if GR_TEST_UTILS 661 SkString onDumpInfo() const override { 662 SkString string; 663 for (const auto& geo : fLines) { 664 string.appendf("Pt0: [%.2f, %.2f], Pt1: [%.2f, %.2f], Width: %.2f, Ival0: %.2f, " 665 "Ival1 : %.2f, Phase: %.2f\n", 666 geo.fPtsRot[0].fX, geo.fPtsRot[0].fY, 667 geo.fPtsRot[1].fX, geo.fPtsRot[1].fY, 668 geo.fSrcStrokeWidth, 669 geo.fIntervals[0], 670 geo.fIntervals[1], 671 geo.fPhase); 672 } 673 string += fProcessorSet.dumpProcessors(); 674 return string; 675 } 676#endif 677 678 const SkPMColor4f& color() const { return fColor; } 679 const SkMatrix& viewMatrix() const { return fLines[0].fViewMatrix; } 680 AAMode aaMode() const { return fAAMode; } 681 bool fullDash() const { return fFullDash; } 682 SkPaint::Cap cap() const { return fCap; } 683 684 SkSTArray<1, LineData, true> fLines; 685 SkPMColor4f fColor; 686 bool fUsesLocalCoords : 1; 687 bool fFullDash : 1; 688 // We use 3 bits for this 3-value enum because MSVS makes the underlying types signed. 689 SkPaint::Cap fCap : 3; 690 AAMode fAAMode; 691 GrProcessorSet fProcessorSet; 692 const GrUserStencilSettings* fStencilSettings; 693 694 GrSimpleMesh* fMesh = nullptr; 695 GrProgramInfo* fProgramInfo = nullptr; 696 697 using INHERITED = GrMeshDrawOp; 698}; 699 700/* 701 * This effect will draw a dotted line (defined as a dashed lined with round caps and no on 702 * interval). The radius of the dots is given by the strokeWidth and the spacing by the DashInfo. 703 * Both of the previous two parameters are in device space. This effect also requires the setting of 704 * a float2 vertex attribute for the the four corners of the bounding rect. This attribute is the 705 * "dash position" of each vertex. In other words it is the vertex coords (in device space) if we 706 * transform the line to be horizontal, with the start of line at the origin then shifted to the 707 * right by half the off interval. The line then goes in the positive x direction. 708 */ 709class DashingCircleEffect : public GrGeometryProcessor { 710public: 711 typedef SkPathEffect::DashInfo DashInfo; 712 713 static GrGeometryProcessor* Make(SkArenaAlloc* arena, 714 const SkPMColor4f&, 715 AAMode aaMode, 716 const SkMatrix& localMatrix, 717 bool usesLocalCoords); 718 719 const char* name() const override { return "DashingCircleEffect"; } 720 721 SkString getShaderDfxInfo() const override; 722 723 void addToKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; 724 725 std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override; 726 727private: 728 class Impl; 729 730 DashingCircleEffect(const SkPMColor4f&, AAMode aaMode, const SkMatrix& localMatrix, 731 bool usesLocalCoords); 732 733 SkPMColor4f fColor; 734 SkMatrix fLocalMatrix; 735 bool fUsesLocalCoords; 736 AAMode fAAMode; 737 738 Attribute fInPosition; 739 Attribute fInDashParams; 740 Attribute fInCircleParams; 741 742 GR_DECLARE_GEOMETRY_PROCESSOR_TEST 743 744 using INHERITED = GrGeometryProcessor; 745}; 746 747////////////////////////////////////////////////////////////////////////////// 748 749class DashingCircleEffect::Impl : public ProgramImpl { 750public: 751 void setData(const GrGLSLProgramDataManager&, 752 const GrShaderCaps&, 753 const GrGeometryProcessor&) override; 754 755private: 756 void onEmitCode(EmitArgs&, GrGPArgs*) override; 757 758 SkMatrix fLocalMatrix = SkMatrix::InvalidMatrix(); 759 SkPMColor4f fColor = SK_PMColor4fILLEGAL; 760 float fPrevRadius = SK_FloatNaN; 761 float fPrevCenterX = SK_FloatNaN; 762 float fPrevIntervalLength = SK_FloatNaN; 763 764 UniformHandle fParamUniform; 765 UniformHandle fColorUniform; 766 UniformHandle fLocalMatrixUniform; 767}; 768 769void DashingCircleEffect::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { 770 const DashingCircleEffect& dce = args.fGeomProc.cast<DashingCircleEffect>(); 771 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; 772 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; 773 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; 774 775 // emit attributes 776 varyingHandler->emitAttributes(dce); 777 778 // XY are dashPos, Z is dashInterval 779 GrGLSLVarying dashParams(kHalf3_GrSLType); 780 varyingHandler->addVarying("DashParam", &dashParams); 781 vertBuilder->codeAppendf("%s = %s;", dashParams.vsOut(), dce.fInDashParams.name()); 782 783 // x refers to circle radius - 0.5, y refers to cicle's center x coord 784 GrGLSLVarying circleParams(kHalf2_GrSLType); 785 varyingHandler->addVarying("CircleParams", &circleParams); 786 vertBuilder->codeAppendf("%s = %s;", circleParams.vsOut(), dce.fInCircleParams.name()); 787 788 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; 789 // Setup pass through color 790 fragBuilder->codeAppendf("half4 %s;", args.fOutputColor); 791 this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform); 792 793 // Setup position 794 WriteOutputPosition(vertBuilder, gpArgs, dce.fInPosition.name()); 795 if (dce.fUsesLocalCoords) { 796 WriteLocalCoord(vertBuilder, 797 uniformHandler, 798 *args.fShaderCaps, 799 gpArgs, 800 dce.fInPosition.asShaderVar(), 801 dce.fLocalMatrix, 802 &fLocalMatrixUniform); 803 } 804 805 // transforms all points so that we can compare them to our test circle 806 fragBuilder->codeAppendf("half xShifted = half(%s.x - floor(%s.x / %s.z) * %s.z);", 807 dashParams.fsIn(), dashParams.fsIn(), dashParams.fsIn(), 808 dashParams.fsIn()); 809 fragBuilder->codeAppendf("half2 fragPosShifted = half2(xShifted, half(%s.y));", 810 dashParams.fsIn()); 811 fragBuilder->codeAppendf("half2 center = half2(%s.y, 0.0);", circleParams.fsIn()); 812 fragBuilder->codeAppend("half dist = length(center - fragPosShifted);"); 813 if (dce.fAAMode != AAMode::kNone) { 814 fragBuilder->codeAppendf("half diff = dist - %s.x;", circleParams.fsIn()); 815 fragBuilder->codeAppend("diff = 1.0 - diff;"); 816 fragBuilder->codeAppend("half alpha = saturate(diff);"); 817 } else { 818 fragBuilder->codeAppendf("half alpha = 1.0;"); 819 fragBuilder->codeAppendf("alpha *= dist < %s.x + 0.5 ? 1.0 : 0.0;", circleParams.fsIn()); 820 } 821 fragBuilder->codeAppendf("half4 %s = half4(alpha);", args.fOutputCoverage); 822} 823 824void DashingCircleEffect::Impl::setData(const GrGLSLProgramDataManager& pdman, 825 const GrShaderCaps& shaderCaps, 826 const GrGeometryProcessor& geomProc) { 827 const DashingCircleEffect& dce = geomProc.cast<DashingCircleEffect>(); 828 if (dce.fColor != fColor) { 829 pdman.set4fv(fColorUniform, 1, dce.fColor.vec()); 830 fColor = dce.fColor; 831 } 832 SetTransform(pdman, shaderCaps, fLocalMatrixUniform, dce.fLocalMatrix, &fLocalMatrix); 833} 834 835////////////////////////////////////////////////////////////////////////////// 836 837GrGeometryProcessor* DashingCircleEffect::Make(SkArenaAlloc* arena, 838 const SkPMColor4f& color, 839 AAMode aaMode, 840 const SkMatrix& localMatrix, 841 bool usesLocalCoords) { 842 return arena->make([&](void* ptr) { 843 return new (ptr) DashingCircleEffect(color, aaMode, localMatrix, usesLocalCoords); 844 }); 845} 846 847SkString DashingCircleEffect::getShaderDfxInfo() const 848{ 849 SkString format; 850 format.printf("ShaderDfx_DashingCircleEffect_%d_%d_%d_%d_%d", fUsesLocalCoords, fAAMode, 851 fLocalMatrix.isIdentity(), fLocalMatrix.isScaleTranslate(), fLocalMatrix.hasPerspective()); 852 return format; 853} 854 855void DashingCircleEffect::addToKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const { 856 uint32_t key = 0; 857 key |= fUsesLocalCoords ? 0x1 : 0x0; 858 key |= static_cast<uint32_t>(fAAMode) << 1; 859 key |= ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix) << 3; 860 b->add32(key); 861} 862 863std::unique_ptr<GrGeometryProcessor::ProgramImpl> DashingCircleEffect::makeProgramImpl( 864 const GrShaderCaps&) const { 865 return std::make_unique<Impl>(); 866} 867 868DashingCircleEffect::DashingCircleEffect(const SkPMColor4f& color, 869 AAMode aaMode, 870 const SkMatrix& localMatrix, 871 bool usesLocalCoords) 872 : INHERITED(kDashingCircleEffect_ClassID) 873 , fColor(color) 874 , fLocalMatrix(localMatrix) 875 , fUsesLocalCoords(usesLocalCoords) 876 , fAAMode(aaMode) { 877 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; 878 fInDashParams = {"inDashParams", kFloat3_GrVertexAttribType, kHalf3_GrSLType}; 879 fInCircleParams = {"inCircleParams", kFloat2_GrVertexAttribType, kHalf2_GrSLType}; 880 this->setVertexAttributes(&fInPosition, 3); 881} 882 883GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DashingCircleEffect); 884 885#if GR_TEST_UTILS 886GrGeometryProcessor* DashingCircleEffect::TestCreate(GrProcessorTestData* d) { 887 AAMode aaMode = static_cast<AAMode>(d->fRandom->nextULessThan(kAAModeCnt)); 888 GrColor color = GrTest::RandomColor(d->fRandom); 889 SkMatrix matrix = GrTest::TestMatrix(d->fRandom); 890 return DashingCircleEffect::Make(d->allocator(), 891 SkPMColor4f::FromBytes_RGBA(color), 892 aaMode, 893 matrix, 894 d->fRandom->nextBool()); 895} 896#endif 897 898////////////////////////////////////////////////////////////////////////////// 899 900/* 901 * This effect will draw a dashed line. The width of the dash is given by the strokeWidth and the 902 * length and spacing by the DashInfo. Both of the previous two parameters are in device space. 903 * This effect also requires the setting of a float2 vertex attribute for the the four corners of the 904 * bounding rect. This attribute is the "dash position" of each vertex. In other words it is the 905 * vertex coords (in device space) if we transform the line to be horizontal, with the start of 906 * line at the origin then shifted to the right by half the off interval. The line then goes in the 907 * positive x direction. 908 */ 909class DashingLineEffect : public GrGeometryProcessor { 910public: 911 typedef SkPathEffect::DashInfo DashInfo; 912 913 static GrGeometryProcessor* Make(SkArenaAlloc* arena, 914 const SkPMColor4f&, 915 AAMode aaMode, 916 const SkMatrix& localMatrix, 917 bool usesLocalCoords); 918 919 const char* name() const override { return "DashingEffect"; } 920 921 SkString getShaderDfxInfo() const override; 922 923 bool usesLocalCoords() const { return fUsesLocalCoords; } 924 925 void addToKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; 926 927 std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override; 928 929private: 930 class Impl; 931 932 DashingLineEffect(const SkPMColor4f&, AAMode aaMode, const SkMatrix& localMatrix, 933 bool usesLocalCoords); 934 935 SkPMColor4f fColor; 936 SkMatrix fLocalMatrix; 937 bool fUsesLocalCoords; 938 AAMode fAAMode; 939 940 Attribute fInPosition; 941 Attribute fInDashParams; 942 Attribute fInRect; 943 944 GR_DECLARE_GEOMETRY_PROCESSOR_TEST 945 946 using INHERITED = GrGeometryProcessor; 947}; 948 949////////////////////////////////////////////////////////////////////////////// 950 951class DashingLineEffect::Impl : public ProgramImpl { 952public: 953 void setData(const GrGLSLProgramDataManager&, 954 const GrShaderCaps&, 955 const GrGeometryProcessor&) override; 956 957private: 958 void onEmitCode(EmitArgs&, GrGPArgs*) override; 959 960 SkPMColor4f fColor = SK_PMColor4fILLEGAL; 961 SkMatrix fLocalMatrix = SkMatrix::InvalidMatrix(); 962 963 UniformHandle fLocalMatrixUniform; 964 UniformHandle fColorUniform; 965}; 966 967void DashingLineEffect::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { 968 const DashingLineEffect& de = args.fGeomProc.cast<DashingLineEffect>(); 969 970 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; 971 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; 972 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; 973 974 // emit attributes 975 varyingHandler->emitAttributes(de); 976 977 // XY refers to dashPos, Z is the dash interval length 978 GrGLSLVarying inDashParams(kFloat3_GrSLType); 979 varyingHandler->addVarying("DashParams", &inDashParams); 980 vertBuilder->codeAppendf("%s = %s;", inDashParams.vsOut(), de.fInDashParams.name()); 981 982 // The rect uniform's xyzw refer to (left + 0.5, top + 0.5, right - 0.5, bottom - 0.5), 983 // respectively. 984 GrGLSLVarying inRectParams(kFloat4_GrSLType); 985 varyingHandler->addVarying("RectParams", &inRectParams); 986 vertBuilder->codeAppendf("%s = %s;", inRectParams.vsOut(), de.fInRect.name()); 987 988 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; 989 // Setup pass through color 990 fragBuilder->codeAppendf("half4 %s;", args.fOutputColor); 991 this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform); 992 993 // Setup position 994 WriteOutputPosition(vertBuilder, gpArgs, de.fInPosition.name()); 995 if (de.usesLocalCoords()) { 996 WriteLocalCoord(vertBuilder, 997 uniformHandler, 998 *args.fShaderCaps, 999 gpArgs, 1000 de.fInPosition.asShaderVar(), 1001 de.fLocalMatrix, 1002 &fLocalMatrixUniform); 1003 } 1004 1005 // transforms all points so that we can compare them to our test rect 1006 fragBuilder->codeAppendf("half xShifted = half(%s.x - floor(%s.x / %s.z) * %s.z);", 1007 inDashParams.fsIn(), inDashParams.fsIn(), inDashParams.fsIn(), 1008 inDashParams.fsIn()); 1009 fragBuilder->codeAppendf("half2 fragPosShifted = half2(xShifted, half(%s.y));", 1010 inDashParams.fsIn()); 1011 if (de.fAAMode == AAMode::kCoverage) { 1012 // The amount of coverage removed in x and y by the edges is computed as a pair of negative 1013 // numbers, xSub and ySub. 1014 fragBuilder->codeAppend("half xSub, ySub;"); 1015 fragBuilder->codeAppendf("xSub = half(min(fragPosShifted.x - %s.x, 0.0));", 1016 inRectParams.fsIn()); 1017 fragBuilder->codeAppendf("xSub += half(min(%s.z - fragPosShifted.x, 0.0));", 1018 inRectParams.fsIn()); 1019 fragBuilder->codeAppendf("ySub = half(min(fragPosShifted.y - %s.y, 0.0));", 1020 inRectParams.fsIn()); 1021 fragBuilder->codeAppendf("ySub += half(min(%s.w - fragPosShifted.y, 0.0));", 1022 inRectParams.fsIn()); 1023 // Now compute coverage in x and y and multiply them to get the fraction of the pixel 1024 // covered. 1025 fragBuilder->codeAppendf( 1026 "half alpha = (1.0 + max(xSub, -1.0)) * (1.0 + max(ySub, -1.0));"); 1027 } else if (de.fAAMode == AAMode::kCoverageWithMSAA) { 1028 // For MSAA, we don't modulate the alpha by the Y distance, since MSAA coverage will handle 1029 // AA on the the top and bottom edges. The shader is only responsible for intra-dash alpha. 1030 fragBuilder->codeAppend("half xSub;"); 1031 fragBuilder->codeAppendf("xSub = half(min(fragPosShifted.x - %s.x, 0.0));", 1032 inRectParams.fsIn()); 1033 fragBuilder->codeAppendf("xSub += half(min(%s.z - fragPosShifted.x, 0.0));", 1034 inRectParams.fsIn()); 1035 // Now compute coverage in x to get the fraction of the pixel covered. 1036 fragBuilder->codeAppendf("half alpha = (1.0 + max(xSub, -1.0));"); 1037 } else { 1038 // Assuming the bounding geometry is tight so no need to check y values 1039 fragBuilder->codeAppendf("half alpha = 1.0;"); 1040 fragBuilder->codeAppendf("alpha *= (fragPosShifted.x - %s.x) > -0.5 ? 1.0 : 0.0;", 1041 inRectParams.fsIn()); 1042 fragBuilder->codeAppendf("alpha *= (%s.z - fragPosShifted.x) >= -0.5 ? 1.0 : 0.0;", 1043 inRectParams.fsIn()); 1044 } 1045 fragBuilder->codeAppendf("half4 %s = half4(alpha);", args.fOutputCoverage); 1046} 1047 1048void DashingLineEffect::Impl::setData(const GrGLSLProgramDataManager& pdman, 1049 const GrShaderCaps& shaderCaps, 1050 const GrGeometryProcessor& geomProc) { 1051 const DashingLineEffect& de = geomProc.cast<DashingLineEffect>(); 1052 if (de.fColor != fColor) { 1053 pdman.set4fv(fColorUniform, 1, de.fColor.vec()); 1054 fColor = de.fColor; 1055 } 1056 SetTransform(pdman, shaderCaps, fLocalMatrixUniform, de.fLocalMatrix, &fLocalMatrix); 1057} 1058 1059////////////////////////////////////////////////////////////////////////////// 1060 1061GrGeometryProcessor* DashingLineEffect::Make(SkArenaAlloc* arena, 1062 const SkPMColor4f& color, 1063 AAMode aaMode, 1064 const SkMatrix& localMatrix, 1065 bool usesLocalCoords) { 1066 return arena->make([&](void* ptr) { 1067 return new (ptr) DashingLineEffect(color, aaMode, localMatrix, usesLocalCoords); 1068 }); 1069} 1070 1071SkString DashingLineEffect::getShaderDfxInfo() const 1072{ 1073 SkString format; 1074 format.printf("ShaderDfx_DashingLineEffect_%d_%d_%d_%d_%d", fUsesLocalCoords, fAAMode, 1075 fLocalMatrix.isIdentity(), fLocalMatrix.isScaleTranslate(), fLocalMatrix.hasPerspective()); 1076 return format; 1077} 1078 1079void DashingLineEffect::addToKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const { 1080 uint32_t key = 0; 1081 key |= fUsesLocalCoords ? 0x1 : 0x0; 1082 key |= static_cast<int>(fAAMode) << 1; 1083 key |= ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix) << 3; 1084 b->add32(key); 1085} 1086 1087std::unique_ptr<GrGeometryProcessor::ProgramImpl> DashingLineEffect::makeProgramImpl( 1088 const GrShaderCaps&) const { 1089 return std::make_unique<Impl>(); 1090} 1091 1092DashingLineEffect::DashingLineEffect(const SkPMColor4f& color, 1093 AAMode aaMode, 1094 const SkMatrix& localMatrix, 1095 bool usesLocalCoords) 1096 : INHERITED(kDashingLineEffect_ClassID) 1097 , fColor(color) 1098 , fLocalMatrix(localMatrix) 1099 , fUsesLocalCoords(usesLocalCoords) 1100 , fAAMode(aaMode) { 1101 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; 1102 fInDashParams = {"inDashParams", kFloat3_GrVertexAttribType, kHalf3_GrSLType}; 1103 fInRect = {"inRect", kFloat4_GrVertexAttribType, kHalf4_GrSLType}; 1104 this->setVertexAttributes(&fInPosition, 3); 1105} 1106 1107GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DashingLineEffect); 1108 1109#if GR_TEST_UTILS 1110GrGeometryProcessor* DashingLineEffect::TestCreate(GrProcessorTestData* d) { 1111 AAMode aaMode = static_cast<AAMode>(d->fRandom->nextULessThan(kAAModeCnt)); 1112 GrColor color = GrTest::RandomColor(d->fRandom); 1113 SkMatrix matrix = GrTest::TestMatrix(d->fRandom); 1114 return DashingLineEffect::Make(d->allocator(), 1115 SkPMColor4f::FromBytes_RGBA(color), 1116 aaMode, 1117 matrix, 1118 d->fRandom->nextBool()); 1119} 1120 1121#endif 1122////////////////////////////////////////////////////////////////////////////// 1123 1124GrGeometryProcessor* make_dash_gp(SkArenaAlloc* arena, 1125 const SkPMColor4f& color, 1126 AAMode aaMode, 1127 DashCap cap, 1128 const SkMatrix& viewMatrix, 1129 bool usesLocalCoords) { 1130 SkMatrix invert; 1131 if (usesLocalCoords && !viewMatrix.invert(&invert)) { 1132 SkDebugf("Failed to invert\n"); 1133 return nullptr; 1134 } 1135 1136 switch (cap) { 1137 case kRound_DashCap: 1138 return DashingCircleEffect::Make(arena, color, aaMode, invert, usesLocalCoords); 1139 case kNonRound_DashCap: 1140 return DashingLineEffect::Make(arena, color, aaMode, invert, usesLocalCoords); 1141 } 1142 return nullptr; 1143} 1144 1145} // anonymous namespace 1146 1147///////////////////////////////////////////////////////////////////////////////////////////////// 1148 1149GrOp::Owner MakeDashLineOp(GrRecordingContext* context, 1150 GrPaint&& paint, 1151 const SkMatrix& viewMatrix, 1152 const SkPoint pts[2], 1153 AAMode aaMode, 1154 const GrStyle& style, 1155 const GrUserStencilSettings* stencilSettings) { 1156 SkASSERT(CanDrawDashLine(pts, style, viewMatrix)); 1157 const SkScalar* intervals = style.dashIntervals(); 1158 SkScalar phase = style.dashPhase(); 1159 1160 SkPaint::Cap cap = style.strokeRec().getCap(); 1161 1162 DashOpImpl::LineData lineData; 1163 lineData.fSrcStrokeWidth = style.strokeRec().getWidth(); 1164 1165 // the phase should be normalized to be [0, sum of all intervals) 1166 SkASSERT(phase >= 0 && phase < intervals[0] + intervals[1]); 1167 1168 // Rotate the src pts so they are aligned horizontally with pts[0].fX < pts[1].fX 1169 if (pts[0].fY != pts[1].fY || pts[0].fX > pts[1].fX) { 1170 SkMatrix rotMatrix; 1171 align_to_x_axis(pts, &rotMatrix, lineData.fPtsRot); 1172 if (!rotMatrix.invert(&lineData.fSrcRotInv)) { 1173 SkDebugf("Failed to create invertible rotation matrix!\n"); 1174 return nullptr; 1175 } 1176 } else { 1177 lineData.fSrcRotInv.reset(); 1178 memcpy(lineData.fPtsRot, pts, 2 * sizeof(SkPoint)); 1179 } 1180 1181 // Scale corrections of intervals and stroke from view matrix 1182 calc_dash_scaling(&lineData.fParallelScale, &lineData.fPerpendicularScale, viewMatrix, pts); 1183 if (SkScalarNearlyZero(lineData.fParallelScale) || 1184 SkScalarNearlyZero(lineData.fPerpendicularScale)) { 1185 return nullptr; 1186 } 1187 1188 SkScalar offInterval = intervals[1] * lineData.fParallelScale; 1189 SkScalar strokeWidth = lineData.fSrcStrokeWidth * lineData.fPerpendicularScale; 1190 1191 if (SkPaint::kSquare_Cap == cap && 0 != lineData.fSrcStrokeWidth) { 1192 // add cap to on interval and remove from off interval 1193 offInterval -= strokeWidth; 1194 } 1195 1196 // TODO we can do a real rect call if not using fulldash(ie no off interval, not using AA) 1197 bool fullDash = offInterval > 0.f || aaMode != AAMode::kNone; 1198 1199 lineData.fViewMatrix = viewMatrix; 1200 lineData.fPhase = phase; 1201 lineData.fIntervals[0] = intervals[0]; 1202 lineData.fIntervals[1] = intervals[1]; 1203 1204 return DashOpImpl::Make(context, std::move(paint), lineData, cap, aaMode, fullDash, 1205 stencilSettings); 1206} 1207 1208// Returns whether or not the gpu can fast path the dash line effect. 1209bool CanDrawDashLine(const SkPoint pts[2], const GrStyle& style, const SkMatrix& viewMatrix) { 1210 // Pts must be either horizontal or vertical in src space 1211 if (pts[0].fX != pts[1].fX && pts[0].fY != pts[1].fY) { 1212 return false; 1213 } 1214 1215 // May be able to relax this to include skew. As of now cannot do perspective 1216 // because of the non uniform scaling of bloating a rect 1217 if (!viewMatrix.preservesRightAngles()) { 1218 return false; 1219 } 1220 1221 if (!style.isDashed() || 2 != style.dashIntervalCnt()) { 1222 return false; 1223 } 1224 1225 const SkScalar* intervals = style.dashIntervals(); 1226 if (0 == intervals[0] && 0 == intervals[1]) { 1227 return false; 1228 } 1229 1230 SkPaint::Cap cap = style.strokeRec().getCap(); 1231 if (SkPaint::kRound_Cap == cap) { 1232 // Current we don't support round caps unless the on interval is zero 1233 if (intervals[0] != 0.f) { 1234 return false; 1235 } 1236 // If the width of the circle caps in greater than the off interval we will pick up unwanted 1237 // segments of circles at the start and end of the dash line. 1238 if (style.strokeRec().getWidth() > intervals[1]) { 1239 return false; 1240 } 1241 } 1242 1243 return true; 1244} 1245 1246} // namespace skgpu::v1::DashOp 1247 1248#if GR_TEST_UTILS 1249 1250#include "src/gpu/GrDrawOpTest.h" 1251 1252GR_DRAW_OP_TEST_DEFINE(DashOpImpl) { 1253 SkMatrix viewMatrix = GrTest::TestMatrixPreservesRightAngles(random); 1254 AAMode aaMode; 1255 do { 1256 aaMode = static_cast<AAMode>(random->nextULessThan(kAAModeCnt)); 1257 } while (AAMode::kCoverageWithMSAA == aaMode && numSamples <= 1); 1258 1259 // We can only dash either horizontal or vertical lines 1260 SkPoint pts[2]; 1261 if (random->nextBool()) { 1262 // vertical 1263 pts[0].fX = 1.f; 1264 pts[0].fY = random->nextF() * 10.f; 1265 pts[1].fX = 1.f; 1266 pts[1].fY = random->nextF() * 10.f; 1267 } else { 1268 // horizontal 1269 pts[0].fX = random->nextF() * 10.f; 1270 pts[0].fY = 1.f; 1271 pts[1].fX = random->nextF() * 10.f; 1272 pts[1].fY = 1.f; 1273 } 1274 1275 // pick random cap 1276 SkPaint::Cap cap = SkPaint::Cap(random->nextULessThan(SkPaint::kCapCount)); 1277 1278 SkScalar intervals[2]; 1279 1280 // We can only dash with the following intervals 1281 enum Intervals { 1282 kOpenOpen_Intervals , 1283 kOpenClose_Intervals, 1284 kCloseOpen_Intervals, 1285 }; 1286 1287 Intervals intervalType = SkPaint::kRound_Cap == cap ? 1288 kOpenClose_Intervals : 1289 Intervals(random->nextULessThan(kCloseOpen_Intervals + 1)); 1290 static const SkScalar kIntervalMin = 0.1f; 1291 static const SkScalar kIntervalMinCircles = 1.f; // Must be >= to stroke width 1292 static const SkScalar kIntervalMax = 10.f; 1293 switch (intervalType) { 1294 case kOpenOpen_Intervals: 1295 intervals[0] = random->nextRangeScalar(kIntervalMin, kIntervalMax); 1296 intervals[1] = random->nextRangeScalar(kIntervalMin, kIntervalMax); 1297 break; 1298 case kOpenClose_Intervals: { 1299 intervals[0] = 0.f; 1300 SkScalar min = SkPaint::kRound_Cap == cap ? kIntervalMinCircles : kIntervalMin; 1301 intervals[1] = random->nextRangeScalar(min, kIntervalMax); 1302 break; 1303 } 1304 case kCloseOpen_Intervals: 1305 intervals[0] = random->nextRangeScalar(kIntervalMin, kIntervalMax); 1306 intervals[1] = 0.f; 1307 break; 1308 1309 } 1310 1311 // phase is 0 < sum (i0, i1) 1312 SkScalar phase = random->nextRangeScalar(0, intervals[0] + intervals[1]); 1313 1314 SkPaint p; 1315 p.setStyle(SkPaint::kStroke_Style); 1316 p.setStrokeWidth(SkIntToScalar(1)); 1317 p.setStrokeCap(cap); 1318 p.setPathEffect(GrTest::TestDashPathEffect::Make(intervals, 2, phase)); 1319 1320 GrStyle style(p); 1321 1322 return skgpu::v1::DashOp::MakeDashLineOp(context, std::move(paint), viewMatrix, pts, aaMode, 1323 style, GrGetRandomStencil(random, context)); 1324} 1325 1326#endif // GR_TEST_UTILS 1327