GrDashingEffect.cpp revision 9853ccef19c200be93a6211f32589fa82a53067c
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 "GrDashingEffect.h" 9 10#include "../GrAARectRenderer.h" 11 12#include "GrGeometryProcessor.h" 13#include "GrContext.h" 14#include "GrCoordTransform.h" 15#include "GrDefaultGeoProcFactory.h" 16#include "GrDrawTarget.h" 17#include "GrDrawTargetCaps.h" 18#include "GrInvariantOutput.h" 19#include "GrProcessor.h" 20#include "GrStrokeInfo.h" 21#include "GrTBackendProcessorFactory.h" 22#include "SkGr.h" 23#include "gl/GrGLGeometryProcessor.h" 24#include "gl/GrGLProcessor.h" 25#include "gl/GrGLSL.h" 26#include "gl/builders/GrGLProgramBuilder.h" 27 28/////////////////////////////////////////////////////////////////////////////// 29 30// Returns whether or not the gpu can fast path the dash line effect. 31static bool can_fast_path_dash(const SkPoint pts[2], const GrStrokeInfo& strokeInfo, 32 const GrDrawTarget& target, const GrDrawState& ds, 33 const SkMatrix& viewMatrix) { 34 if (ds.getRenderTarget()->isMultisampled()) { 35 return false; 36 } 37 38 // Pts must be either horizontal or vertical in src space 39 if (pts[0].fX != pts[1].fX && pts[0].fY != pts[1].fY) { 40 return false; 41 } 42 43 // May be able to relax this to include skew. As of now cannot do perspective 44 // because of the non uniform scaling of bloating a rect 45 if (!viewMatrix.preservesRightAngles()) { 46 return false; 47 } 48 49 if (!strokeInfo.isDashed() || 2 != strokeInfo.dashCount()) { 50 return false; 51 } 52 53 const SkPathEffect::DashInfo& info = strokeInfo.getDashInfo(); 54 if (0 == info.fIntervals[0] && 0 == info.fIntervals[1]) { 55 return false; 56 } 57 58 SkPaint::Cap cap = strokeInfo.getStrokeRec().getCap(); 59 // Current we do don't handle Round or Square cap dashes 60 if (SkPaint::kRound_Cap == cap && info.fIntervals[0] != 0.f) { 61 return false; 62 } 63 64 return true; 65} 66 67namespace { 68 69struct DashLineVertex { 70 SkPoint fPos; 71 SkPoint fDashPos; 72}; 73 74extern const GrVertexAttrib gDashLineVertexAttribs[] = { 75 { kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding }, 76 { kVec2f_GrVertexAttribType, sizeof(SkPoint), kGeometryProcessor_GrVertexAttribBinding }, 77}; 78 79}; 80static void calc_dash_scaling(SkScalar* parallelScale, SkScalar* perpScale, 81 const SkMatrix& viewMatrix, const SkPoint pts[2]) { 82 SkVector vecSrc = pts[1] - pts[0]; 83 SkScalar magSrc = vecSrc.length(); 84 SkScalar invSrc = magSrc ? SkScalarInvert(magSrc) : 0; 85 vecSrc.scale(invSrc); 86 87 SkVector vecSrcPerp; 88 vecSrc.rotateCW(&vecSrcPerp); 89 viewMatrix.mapVectors(&vecSrc, 1); 90 viewMatrix.mapVectors(&vecSrcPerp, 1); 91 92 // parallelScale tells how much to scale along the line parallel to the dash line 93 // perpScale tells how much to scale in the direction perpendicular to the dash line 94 *parallelScale = vecSrc.length(); 95 *perpScale = vecSrcPerp.length(); 96} 97 98// calculates the rotation needed to aligned pts to the x axis with pts[0] < pts[1] 99// Stores the rotation matrix in rotMatrix, and the mapped points in ptsRot 100static void align_to_x_axis(const SkPoint pts[2], SkMatrix* rotMatrix, SkPoint ptsRot[2] = NULL) { 101 SkVector vec = pts[1] - pts[0]; 102 SkScalar mag = vec.length(); 103 SkScalar inv = mag ? SkScalarInvert(mag) : 0; 104 105 vec.scale(inv); 106 rotMatrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY); 107 if (ptsRot) { 108 rotMatrix->mapPoints(ptsRot, pts, 2); 109 // correction for numerical issues if map doesn't make ptsRot exactly horizontal 110 ptsRot[1].fY = pts[0].fY; 111 } 112} 113 114// Assumes phase < sum of all intervals 115static SkScalar calc_start_adjustment(const SkPathEffect::DashInfo& info) { 116 SkASSERT(info.fPhase < info.fIntervals[0] + info.fIntervals[1]); 117 if (info.fPhase >= info.fIntervals[0] && info.fPhase != 0) { 118 SkScalar srcIntervalLen = info.fIntervals[0] + info.fIntervals[1]; 119 return srcIntervalLen - info.fPhase; 120 } 121 return 0; 122} 123 124static SkScalar calc_end_adjustment(const SkPathEffect::DashInfo& info, const SkPoint pts[2], 125 SkScalar phase, SkScalar* endingInt) { 126 if (pts[1].fX <= pts[0].fX) { 127 return 0; 128 } 129 SkScalar srcIntervalLen = info.fIntervals[0] + info.fIntervals[1]; 130 SkScalar totalLen = pts[1].fX - pts[0].fX; 131 SkScalar temp = SkScalarDiv(totalLen, srcIntervalLen); 132 SkScalar numFullIntervals = SkScalarFloorToScalar(temp); 133 *endingInt = totalLen - numFullIntervals * srcIntervalLen + phase; 134 temp = SkScalarDiv(*endingInt, srcIntervalLen); 135 *endingInt = *endingInt - SkScalarFloorToScalar(temp) * srcIntervalLen; 136 if (0 == *endingInt) { 137 *endingInt = srcIntervalLen; 138 } 139 if (*endingInt > info.fIntervals[0]) { 140 if (0 == info.fIntervals[0]) { 141 *endingInt -= 0.01f; // make sure we capture the last zero size pnt (used if has caps) 142 } 143 return *endingInt - info.fIntervals[0]; 144 } 145 return 0; 146} 147 148static void setup_dashed_rect(const SkRect& rect, DashLineVertex* verts, int idx, const SkMatrix& matrix, 149 SkScalar offset, SkScalar bloat, SkScalar len, SkScalar stroke) { 150 SkScalar startDashX = offset - bloat; 151 SkScalar endDashX = offset + len + bloat; 152 SkScalar startDashY = -stroke - bloat; 153 SkScalar endDashY = stroke + bloat; 154 verts[idx].fDashPos = SkPoint::Make(startDashX , startDashY); 155 verts[idx + 1].fDashPos = SkPoint::Make(startDashX, endDashY); 156 verts[idx + 2].fDashPos = SkPoint::Make(endDashX, endDashY); 157 verts[idx + 3].fDashPos = SkPoint::Make(endDashX, startDashY); 158 verts[idx].fPos = SkPoint::Make(rect.fLeft, rect.fTop); 159 verts[idx + 1].fPos = SkPoint::Make(rect.fLeft, rect.fBottom); 160 verts[idx + 2].fPos = SkPoint::Make(rect.fRight, rect.fBottom); 161 verts[idx + 3].fPos = SkPoint::Make(rect.fRight, rect.fTop); 162 matrix.mapPointsWithStride(&verts[idx].fPos, sizeof(DashLineVertex), 4); 163} 164 165static void setup_dashed_rect_pos(const SkRect& rect, int idx, const SkMatrix& matrix, 166 SkPoint* verts) { 167 verts[idx] = SkPoint::Make(rect.fLeft, rect.fTop); 168 verts[idx + 1] = SkPoint::Make(rect.fLeft, rect.fBottom); 169 verts[idx + 2] = SkPoint::Make(rect.fRight, rect.fBottom); 170 verts[idx + 3] = SkPoint::Make(rect.fRight, rect.fTop); 171 matrix.mapPoints(&verts[idx], 4); 172} 173 174bool GrDashingEffect::DrawDashLine(GrGpu* gpu, GrDrawTarget* target, GrDrawState* drawState, 175 const SkPoint pts[2], const GrPaint& paint, 176 const GrStrokeInfo& strokeInfo, const SkMatrix& vm) { 177 178 if (!can_fast_path_dash(pts, strokeInfo, *target, *drawState, vm)) { 179 return false; 180 } 181 182 const SkPathEffect::DashInfo& info = strokeInfo.getDashInfo(); 183 184 SkPaint::Cap cap = strokeInfo.getStrokeRec().getCap(); 185 186 SkScalar srcStrokeWidth = strokeInfo.getStrokeRec().getWidth(); 187 188 // the phase should be normalized to be [0, sum of all intervals) 189 SkASSERT(info.fPhase >= 0 && info.fPhase < info.fIntervals[0] + info.fIntervals[1]); 190 191 SkScalar srcPhase = info.fPhase; 192 193 // Rotate the src pts so they are aligned horizontally with pts[0].fX < pts[1].fX 194 SkMatrix srcRotInv; 195 SkPoint ptsRot[2]; 196 if (pts[0].fY != pts[1].fY || pts[0].fX > pts[1].fX) { 197 SkMatrix rotMatrix; 198 align_to_x_axis(pts, &rotMatrix, ptsRot); 199 if(!rotMatrix.invert(&srcRotInv)) { 200 SkDebugf("Failed to create invertible rotation matrix!\n"); 201 return false; 202 } 203 } else { 204 srcRotInv.reset(); 205 memcpy(ptsRot, pts, 2 * sizeof(SkPoint)); 206 } 207 208 bool useAA = paint.isAntiAlias(); 209 210 // Scale corrections of intervals and stroke from view matrix 211 SkScalar parallelScale; 212 SkScalar perpScale; 213 calc_dash_scaling(¶llelScale, &perpScale, vm, ptsRot); 214 215 bool hasCap = SkPaint::kButt_Cap != cap && 0 != srcStrokeWidth; 216 217 // We always want to at least stroke out half a pixel on each side in device space 218 // so 0.5f / perpScale gives us this min in src space 219 SkScalar halfSrcStroke = SkMaxScalar(srcStrokeWidth * 0.5f, 0.5f / perpScale); 220 221 SkScalar strokeAdj; 222 if (!hasCap) { 223 strokeAdj = 0.f; 224 } else { 225 strokeAdj = halfSrcStroke; 226 } 227 228 SkScalar startAdj = 0; 229 230 SkMatrix combinedMatrix = srcRotInv; 231 combinedMatrix.postConcat(vm); 232 233 bool lineDone = false; 234 SkRect startRect; 235 bool hasStartRect = false; 236 // If we are using AA, check to see if we are drawing a partial dash at the start. If so 237 // draw it separately here and adjust our start point accordingly 238 if (useAA) { 239 if (srcPhase > 0 && srcPhase < info.fIntervals[0]) { 240 SkPoint startPts[2]; 241 startPts[0] = ptsRot[0]; 242 startPts[1].fY = startPts[0].fY; 243 startPts[1].fX = SkMinScalar(startPts[0].fX + info.fIntervals[0] - srcPhase, 244 ptsRot[1].fX); 245 startRect.set(startPts, 2); 246 startRect.outset(strokeAdj, halfSrcStroke); 247 248 hasStartRect = true; 249 startAdj = info.fIntervals[0] + info.fIntervals[1] - srcPhase; 250 } 251 } 252 253 // adjustments for start and end of bounding rect so we only draw dash intervals 254 // contained in the original line segment. 255 startAdj += calc_start_adjustment(info); 256 if (startAdj != 0) { 257 ptsRot[0].fX += startAdj; 258 srcPhase = 0; 259 } 260 SkScalar endingInterval = 0; 261 SkScalar endAdj = calc_end_adjustment(info, ptsRot, srcPhase, &endingInterval); 262 ptsRot[1].fX -= endAdj; 263 if (ptsRot[0].fX >= ptsRot[1].fX) { 264 lineDone = true; 265 } 266 267 SkRect endRect; 268 bool hasEndRect = false; 269 // If we are using AA, check to see if we are drawing a partial dash at then end. If so 270 // draw it separately here and adjust our end point accordingly 271 if (useAA && !lineDone) { 272 // If we adjusted the end then we will not be drawing a partial dash at the end. 273 // If we didn't adjust the end point then we just need to make sure the ending 274 // dash isn't a full dash 275 if (0 == endAdj && endingInterval != info.fIntervals[0]) { 276 SkPoint endPts[2]; 277 endPts[1] = ptsRot[1]; 278 endPts[0].fY = endPts[1].fY; 279 endPts[0].fX = endPts[1].fX - endingInterval; 280 281 endRect.set(endPts, 2); 282 endRect.outset(strokeAdj, halfSrcStroke); 283 284 hasEndRect = true; 285 endAdj = endingInterval + info.fIntervals[1]; 286 287 ptsRot[1].fX -= endAdj; 288 if (ptsRot[0].fX >= ptsRot[1].fX) { 289 lineDone = true; 290 } 291 } 292 } 293 294 if (startAdj != 0) { 295 srcPhase = 0; 296 } 297 298 // Change the dashing info from src space into device space 299 SkScalar devIntervals[2]; 300 devIntervals[0] = info.fIntervals[0] * parallelScale; 301 devIntervals[1] = info.fIntervals[1] * parallelScale; 302 SkScalar devPhase = srcPhase * parallelScale; 303 SkScalar strokeWidth = srcStrokeWidth * perpScale; 304 305 if ((strokeWidth < 1.f && !useAA) || 0.f == strokeWidth) { 306 strokeWidth = 1.f; 307 } 308 309 SkScalar halfDevStroke = strokeWidth * 0.5f; 310 311 if (SkPaint::kSquare_Cap == cap && 0 != srcStrokeWidth) { 312 // add cap to on interveal and remove from off interval 313 devIntervals[0] += strokeWidth; 314 devIntervals[1] -= strokeWidth; 315 } 316 SkScalar startOffset = devIntervals[1] * 0.5f + devPhase; 317 318 SkScalar bloatX = useAA ? 0.5f / parallelScale : 0.f; 319 SkScalar bloatY = useAA ? 0.5f / perpScale : 0.f; 320 321 SkScalar devBloat = useAA ? 0.5f : 0.f; 322 323 if (devIntervals[1] <= 0.f && useAA) { 324 // Case when we end up drawing a solid AA rect 325 // Reset the start rect to draw this single solid rect 326 // but it requires to upload a new intervals uniform so we can mimic 327 // one giant dash 328 ptsRot[0].fX -= hasStartRect ? startAdj : 0; 329 ptsRot[1].fX += hasEndRect ? endAdj : 0; 330 startRect.set(ptsRot, 2); 331 startRect.outset(strokeAdj, halfSrcStroke); 332 hasStartRect = true; 333 hasEndRect = false; 334 lineDone = true; 335 336 SkPoint devicePts[2]; 337 vm.mapPoints(devicePts, ptsRot, 2); 338 SkScalar lineLength = SkPoint::Distance(devicePts[0], devicePts[1]); 339 if (hasCap) { 340 lineLength += 2.f * halfDevStroke; 341 } 342 devIntervals[0] = lineLength; 343 } 344 bool fullDash = devIntervals[1] > 0.f || useAA; 345 if (fullDash) { 346 SkPathEffect::DashInfo devInfo; 347 devInfo.fPhase = devPhase; 348 devInfo.fCount = 2; 349 devInfo.fIntervals = devIntervals; 350 GrPrimitiveEdgeType edgeType= useAA ? kFillAA_GrProcessorEdgeType : 351 kFillBW_GrProcessorEdgeType; 352 bool isRoundCap = SkPaint::kRound_Cap == cap; 353 GrDashingEffect::DashCap capType = isRoundCap ? GrDashingEffect::kRound_DashCap : 354 GrDashingEffect::kNonRound_DashCap; 355 drawState->setGeometryProcessor( 356 GrDashingEffect::Create(edgeType, devInfo, strokeWidth, capType))->unref(); 357 358 // Set up the vertex data for the line and start/end dashes 359 drawState->setVertexAttribs<gDashLineVertexAttribs>(SK_ARRAY_COUNT(gDashLineVertexAttribs), 360 sizeof(DashLineVertex)); 361 } else { 362 // Set up the vertex data for the line and start/end dashes 363 drawState->setGeometryProcessor( 364 GrDefaultGeoProcFactory::CreateAndSetAttribs( 365 drawState, 366 GrDefaultGeoProcFactory::kPosition_GPType))->unref(); 367 } 368 369 int totalRectCnt = 0; 370 371 totalRectCnt += !lineDone ? 1 : 0; 372 totalRectCnt += hasStartRect ? 1 : 0; 373 totalRectCnt += hasEndRect ? 1 : 0; 374 375 GrDrawTarget::AutoReleaseGeometry geo(target, 376 totalRectCnt * 4, 377 drawState->getVertexStride(), 0); 378 if (!geo.succeeded()) { 379 SkDebugf("Failed to get space for vertices!\n"); 380 return false; 381 } 382 383 int curVIdx = 0; 384 385 if (SkPaint::kRound_Cap == cap && 0 != srcStrokeWidth) { 386 // need to adjust this for round caps to correctly set the dashPos attrib on vertices 387 startOffset -= halfDevStroke; 388 } 389 390 // Draw interior part of dashed line 391 if (!lineDone) { 392 SkPoint devicePts[2]; 393 vm.mapPoints(devicePts, ptsRot, 2); 394 SkScalar lineLength = SkPoint::Distance(devicePts[0], devicePts[1]); 395 if (hasCap) { 396 lineLength += 2.f * halfDevStroke; 397 } 398 399 SkRect bounds; 400 bounds.set(ptsRot[0].fX, ptsRot[0].fY, ptsRot[1].fX, ptsRot[1].fY); 401 bounds.outset(bloatX + strokeAdj, bloatY + halfSrcStroke); 402 if (fullDash) { 403 DashLineVertex* verts = reinterpret_cast<DashLineVertex*>(geo.vertices()); 404 setup_dashed_rect(bounds, verts, curVIdx, combinedMatrix, startOffset, devBloat, 405 lineLength, halfDevStroke); 406 } else { 407 SkPoint* verts = reinterpret_cast<SkPoint*>(geo.vertices()); 408 setup_dashed_rect_pos(bounds, curVIdx, combinedMatrix, verts); 409 } 410 curVIdx += 4; 411 } 412 413 if (hasStartRect) { 414 SkASSERT(useAA); // so that we know bloatX and bloatY have been set 415 startRect.outset(bloatX, bloatY); 416 if (fullDash) { 417 DashLineVertex* verts = reinterpret_cast<DashLineVertex*>(geo.vertices()); 418 setup_dashed_rect(startRect, verts, curVIdx, combinedMatrix, startOffset, devBloat, 419 devIntervals[0], halfDevStroke); 420 } else { 421 SkPoint* verts = reinterpret_cast<SkPoint*>(geo.vertices()); 422 setup_dashed_rect_pos(startRect, curVIdx, combinedMatrix, verts); 423 } 424 425 curVIdx += 4; 426 } 427 428 if (hasEndRect) { 429 SkASSERT(useAA); // so that we know bloatX and bloatY have been set 430 endRect.outset(bloatX, bloatY); 431 if (fullDash) { 432 DashLineVertex* verts = reinterpret_cast<DashLineVertex*>(geo.vertices()); 433 setup_dashed_rect(endRect, verts, curVIdx, combinedMatrix, startOffset, devBloat, 434 devIntervals[0], halfDevStroke); 435 } else { 436 SkPoint* verts = reinterpret_cast<SkPoint*>(geo.vertices()); 437 setup_dashed_rect_pos(endRect, curVIdx, combinedMatrix, verts); 438 } 439 440 } 441 442 target->setIndexSourceToBuffer(gpu->getContext()->getQuadIndexBuffer()); 443 target->drawIndexedInstances(drawState, kTriangles_GrPrimitiveType, totalRectCnt, 4, 6); 444 target->resetIndexSource(); 445 return true; 446} 447 448////////////////////////////////////////////////////////////////////////////// 449 450class GLDashingCircleEffect; 451/* 452 * This effect will draw a dotted line (defined as a dashed lined with round caps and no on 453 * interval). The radius of the dots is given by the strokeWidth and the spacing by the DashInfo. 454 * Both of the previous two parameters are in device space. This effect also requires the setting of 455 * a vec2 vertex attribute for the the four corners of the bounding rect. This attribute is the 456 * "dash position" of each vertex. In other words it is the vertex coords (in device space) if we 457 * transform the line to be horizontal, with the start of line at the origin then shifted to the 458 * right by half the off interval. The line then goes in the positive x direction. 459 */ 460class DashingCircleEffect : public GrGeometryProcessor { 461public: 462 typedef SkPathEffect::DashInfo DashInfo; 463 464 static GrGeometryProcessor* Create(GrPrimitiveEdgeType edgeType, 465 const DashInfo& info, 466 SkScalar radius); 467 468 virtual ~DashingCircleEffect(); 469 470 static const char* Name() { return "DashingCircleEffect"; } 471 472 const GrShaderVar& inCoord() const { return fInCoord; } 473 474 GrPrimitiveEdgeType getEdgeType() const { return fEdgeType; } 475 476 SkScalar getRadius() const { return fRadius; } 477 478 SkScalar getCenterX() const { return fCenterX; } 479 480 SkScalar getIntervalLength() const { return fIntervalLength; } 481 482 typedef GLDashingCircleEffect GLProcessor; 483 484 virtual const GrBackendGeometryProcessorFactory& getFactory() const SK_OVERRIDE; 485 486private: 487 DashingCircleEffect(GrPrimitiveEdgeType edgeType, const DashInfo& info, SkScalar radius); 488 489 virtual bool onIsEqual(const GrGeometryProcessor& other) const SK_OVERRIDE; 490 491 virtual void onComputeInvariantOutput(GrInvariantOutput* inout) const SK_OVERRIDE; 492 493 GrPrimitiveEdgeType fEdgeType; 494 const GrShaderVar& fInCoord; 495 SkScalar fIntervalLength; 496 SkScalar fRadius; 497 SkScalar fCenterX; 498 499 GR_DECLARE_GEOMETRY_PROCESSOR_TEST; 500 501 typedef GrGeometryProcessor INHERITED; 502}; 503 504////////////////////////////////////////////////////////////////////////////// 505 506class GLDashingCircleEffect : public GrGLGeometryProcessor { 507public: 508 GLDashingCircleEffect(const GrBackendProcessorFactory&, const GrProcessor&); 509 510 virtual void emitCode(const EmitArgs&) SK_OVERRIDE; 511 512 static inline void GenKey(const GrProcessor&, const GrGLCaps&, GrProcessorKeyBuilder*); 513 514 virtual void setData(const GrGLProgramDataManager&, const GrProcessor&) SK_OVERRIDE; 515 516private: 517 GrGLProgramDataManager::UniformHandle fParamUniform; 518 SkScalar fPrevRadius; 519 SkScalar fPrevCenterX; 520 SkScalar fPrevIntervalLength; 521 typedef GrGLGeometryProcessor INHERITED; 522}; 523 524GLDashingCircleEffect::GLDashingCircleEffect(const GrBackendProcessorFactory& factory, 525 const GrProcessor&) 526 : INHERITED (factory) { 527 fPrevRadius = SK_ScalarMin; 528 fPrevCenterX = SK_ScalarMin; 529 fPrevIntervalLength = SK_ScalarMax; 530} 531 532void GLDashingCircleEffect::emitCode(const EmitArgs& args) { 533 const DashingCircleEffect& dce = args.fGP.cast<DashingCircleEffect>(); 534 const char *paramName; 535 // The param uniforms, xyz, refer to circle radius - 0.5, cicles center x coord, and 536 // the total interval length of the dash. 537 fParamUniform = args.fPB->addUniform(GrGLProgramBuilder::kFragment_Visibility, 538 kVec3f_GrSLType, 539 "params", 540 ¶mName); 541 542 GrGLVertToFrag v(kVec2f_GrSLType); 543 args.fPB->addVarying("Coord", &v); 544 545 GrGLVertexBuilder* vsBuilder = args.fPB->getVertexShaderBuilder(); 546 vsBuilder->codeAppendf("\t%s = %s;\n", v.vsOut(), dce.inCoord().c_str()); 547 548 // setup position varying 549 vsBuilder->codeAppendf("%s = %s * vec3(%s, 1);", vsBuilder->glPosition(), vsBuilder->uViewM(), 550 vsBuilder->inPosition()); 551 552 // transforms all points so that we can compare them to our test circle 553 GrGLGPFragmentBuilder* fsBuilder = args.fPB->getFragmentShaderBuilder(); 554 fsBuilder->codeAppendf("\t\tfloat xShifted = %s.x - floor(%s.x / %s.z) * %s.z;\n", 555 v.fsIn(), v.fsIn(), paramName, paramName); 556 fsBuilder->codeAppendf("\t\tvec2 fragPosShifted = vec2(xShifted, %s.y);\n", v.fsIn()); 557 fsBuilder->codeAppendf("\t\tvec2 center = vec2(%s.y, 0.0);\n", paramName); 558 fsBuilder->codeAppend("\t\tfloat dist = length(center - fragPosShifted);\n"); 559 if (GrProcessorEdgeTypeIsAA(dce.getEdgeType())) { 560 fsBuilder->codeAppendf("\t\tfloat diff = dist - %s.x;\n", paramName); 561 fsBuilder->codeAppend("\t\tdiff = 1.0 - diff;\n"); 562 fsBuilder->codeAppend("\t\tfloat alpha = clamp(diff, 0.0, 1.0);\n"); 563 } else { 564 fsBuilder->codeAppendf("\t\tfloat alpha = 1.0;\n"); 565 fsBuilder->codeAppendf("\t\talpha *= dist < %s.x + 0.5 ? 1.0 : 0.0;\n", paramName); 566 } 567 fsBuilder->codeAppendf("\t\t%s = %s;\n", args.fOutput, 568 (GrGLSLExpr4(args.fInput) * GrGLSLExpr1("alpha")).c_str()); 569} 570 571void GLDashingCircleEffect::setData(const GrGLProgramDataManager& pdman 572 , const GrProcessor& processor) { 573 const DashingCircleEffect& dce = processor.cast<DashingCircleEffect>(); 574 SkScalar radius = dce.getRadius(); 575 SkScalar centerX = dce.getCenterX(); 576 SkScalar intervalLength = dce.getIntervalLength(); 577 if (radius != fPrevRadius || centerX != fPrevCenterX || intervalLength != fPrevIntervalLength) { 578 pdman.set3f(fParamUniform, radius - 0.5f, centerX, intervalLength); 579 fPrevRadius = radius; 580 fPrevCenterX = centerX; 581 fPrevIntervalLength = intervalLength; 582 } 583} 584 585void GLDashingCircleEffect::GenKey(const GrProcessor& processor, const GrGLCaps&, 586 GrProcessorKeyBuilder* b) { 587 const DashingCircleEffect& dce = processor.cast<DashingCircleEffect>(); 588 b->add32(dce.getEdgeType()); 589} 590 591////////////////////////////////////////////////////////////////////////////// 592 593GrGeometryProcessor* DashingCircleEffect::Create(GrPrimitiveEdgeType edgeType, const DashInfo& info, 594 SkScalar radius) { 595 if (info.fCount != 2 || info.fIntervals[0] != 0) { 596 return NULL; 597 } 598 599 return SkNEW_ARGS(DashingCircleEffect, (edgeType, info, radius)); 600} 601 602DashingCircleEffect::~DashingCircleEffect() {} 603 604void DashingCircleEffect::onComputeInvariantOutput(GrInvariantOutput* inout) const { 605 inout->mulByUnknownAlpha(); 606} 607 608const GrBackendGeometryProcessorFactory& DashingCircleEffect::getFactory() const { 609 return GrTBackendGeometryProcessorFactory<DashingCircleEffect>::getInstance(); 610} 611 612DashingCircleEffect::DashingCircleEffect(GrPrimitiveEdgeType edgeType, const DashInfo& info, 613 SkScalar radius) 614 : fEdgeType(edgeType) 615 , fInCoord(this->addVertexAttrib(GrShaderVar("inCoord", 616 kVec2f_GrSLType, 617 GrShaderVar::kAttribute_TypeModifier))) { 618 SkScalar onLen = info.fIntervals[0]; 619 SkScalar offLen = info.fIntervals[1]; 620 fIntervalLength = onLen + offLen; 621 fRadius = radius; 622 fCenterX = SkScalarHalf(offLen); 623} 624 625bool DashingCircleEffect::onIsEqual(const GrGeometryProcessor& other) const { 626 const DashingCircleEffect& dce = other.cast<DashingCircleEffect>(); 627 return (fEdgeType == dce.fEdgeType && 628 fIntervalLength == dce.fIntervalLength && 629 fRadius == dce.fRadius && 630 fCenterX == dce.fCenterX); 631} 632 633GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DashingCircleEffect); 634 635GrGeometryProcessor* DashingCircleEffect::TestCreate(SkRandom* random, 636 GrContext*, 637 const GrDrawTargetCaps& caps, 638 GrTexture*[]) { 639 GrPrimitiveEdgeType edgeType = static_cast<GrPrimitiveEdgeType>(random->nextULessThan( 640 kGrProcessorEdgeTypeCnt)); 641 SkScalar strokeWidth = random->nextRangeScalar(0, 100.f); 642 DashInfo info; 643 info.fCount = 2; 644 SkAutoTArray<SkScalar> intervals(info.fCount); 645 info.fIntervals = intervals.get(); 646 info.fIntervals[0] = 0; 647 info.fIntervals[1] = random->nextRangeScalar(0, 10.f); 648 info.fPhase = random->nextRangeScalar(0, info.fIntervals[1]); 649 650 return DashingCircleEffect::Create(edgeType, info, strokeWidth); 651} 652 653////////////////////////////////////////////////////////////////////////////// 654 655class GLDashingLineEffect; 656 657/* 658 * This effect will draw a dashed line. The width of the dash is given by the strokeWidth and the 659 * length and spacing by the DashInfo. Both of the previous two parameters are in device space. 660 * This effect also requires the setting of a vec2 vertex attribute for the the four corners of the 661 * bounding rect. This attribute is the "dash position" of each vertex. In other words it is the 662 * vertex coords (in device space) if we transform the line to be horizontal, with the start of 663 * line at the origin then shifted to the right by half the off interval. The line then goes in the 664 * positive x direction. 665 */ 666class DashingLineEffect : public GrGeometryProcessor { 667public: 668 typedef SkPathEffect::DashInfo DashInfo; 669 670 static GrGeometryProcessor* Create(GrPrimitiveEdgeType edgeType, 671 const DashInfo& info, 672 SkScalar strokeWidth); 673 674 virtual ~DashingLineEffect(); 675 676 static const char* Name() { return "DashingEffect"; } 677 678 const GrShaderVar& inCoord() const { return fInCoord; } 679 680 GrPrimitiveEdgeType getEdgeType() const { return fEdgeType; } 681 682 const SkRect& getRect() const { return fRect; } 683 684 SkScalar getIntervalLength() const { return fIntervalLength; } 685 686 typedef GLDashingLineEffect GLProcessor; 687 688 virtual const GrBackendGeometryProcessorFactory& getFactory() const SK_OVERRIDE; 689 690private: 691 DashingLineEffect(GrPrimitiveEdgeType edgeType, const DashInfo& info, SkScalar strokeWidth); 692 693 virtual bool onIsEqual(const GrGeometryProcessor& other) const SK_OVERRIDE; 694 695 virtual void onComputeInvariantOutput(GrInvariantOutput* inout) const SK_OVERRIDE; 696 697 GrPrimitiveEdgeType fEdgeType; 698 const GrShaderVar& fInCoord; 699 SkRect fRect; 700 SkScalar fIntervalLength; 701 702 GR_DECLARE_GEOMETRY_PROCESSOR_TEST; 703 704 typedef GrGeometryProcessor INHERITED; 705}; 706 707////////////////////////////////////////////////////////////////////////////// 708 709class GLDashingLineEffect : public GrGLGeometryProcessor { 710public: 711 GLDashingLineEffect(const GrBackendProcessorFactory&, const GrProcessor&); 712 713 virtual void emitCode(const EmitArgs&) SK_OVERRIDE; 714 715 static inline void GenKey(const GrProcessor&, const GrGLCaps&, GrProcessorKeyBuilder*); 716 717 virtual void setData(const GrGLProgramDataManager&, const GrProcessor&) SK_OVERRIDE; 718 719private: 720 GrGLProgramDataManager::UniformHandle fRectUniform; 721 GrGLProgramDataManager::UniformHandle fIntervalUniform; 722 SkRect fPrevRect; 723 SkScalar fPrevIntervalLength; 724 typedef GrGLGeometryProcessor INHERITED; 725}; 726 727GLDashingLineEffect::GLDashingLineEffect(const GrBackendProcessorFactory& factory, 728 const GrProcessor&) 729 : INHERITED (factory) { 730 fPrevRect.fLeft = SK_ScalarNaN; 731 fPrevIntervalLength = SK_ScalarMax; 732} 733 734void GLDashingLineEffect::emitCode(const EmitArgs& args) { 735 const DashingLineEffect& de = args.fGP.cast<DashingLineEffect>(); 736 const char *rectName; 737 // The rect uniform's xyzw refer to (left + 0.5, top + 0.5, right - 0.5, bottom - 0.5), 738 // respectively. 739 fRectUniform = args.fPB->addUniform(GrGLProgramBuilder::kFragment_Visibility, 740 kVec4f_GrSLType, 741 "rect", 742 &rectName); 743 const char *intervalName; 744 // The interval uniform's refers to the total length of the interval (on + off) 745 fIntervalUniform = args.fPB->addUniform(GrGLProgramBuilder::kFragment_Visibility, 746 kFloat_GrSLType, 747 "interval", 748 &intervalName); 749 750 GrGLVertToFrag v(kVec2f_GrSLType); 751 args.fPB->addVarying("Coord", &v); 752 GrGLVertexBuilder* vsBuilder = args.fPB->getVertexShaderBuilder(); 753 vsBuilder->codeAppendf("\t%s = %s;\n", v.vsOut(), de.inCoord().c_str()); 754 755 // setup position varying 756 vsBuilder->codeAppendf("%s = %s * vec3(%s, 1);", vsBuilder->glPosition(), vsBuilder->uViewM(), 757 vsBuilder->inPosition()); 758 759 // transforms all points so that we can compare them to our test rect 760 GrGLGPFragmentBuilder* fsBuilder = args.fPB->getFragmentShaderBuilder(); 761 fsBuilder->codeAppendf("\t\tfloat xShifted = %s.x - floor(%s.x / %s) * %s;\n", 762 v.fsIn(), v.fsIn(), intervalName, intervalName); 763 fsBuilder->codeAppendf("\t\tvec2 fragPosShifted = vec2(xShifted, %s.y);\n", v.fsIn()); 764 if (GrProcessorEdgeTypeIsAA(de.getEdgeType())) { 765 // The amount of coverage removed in x and y by the edges is computed as a pair of negative 766 // numbers, xSub and ySub. 767 fsBuilder->codeAppend("\t\tfloat xSub, ySub;\n"); 768 fsBuilder->codeAppendf("\t\txSub = min(fragPosShifted.x - %s.x, 0.0);\n", rectName); 769 fsBuilder->codeAppendf("\t\txSub += min(%s.z - fragPosShifted.x, 0.0);\n", rectName); 770 fsBuilder->codeAppendf("\t\tySub = min(fragPosShifted.y - %s.y, 0.0);\n", rectName); 771 fsBuilder->codeAppendf("\t\tySub += min(%s.w - fragPosShifted.y, 0.0);\n", rectName); 772 // Now compute coverage in x and y and multiply them to get the fraction of the pixel 773 // covered. 774 fsBuilder->codeAppendf("\t\tfloat alpha = (1.0 + max(xSub, -1.0)) * (1.0 + max(ySub, -1.0));\n"); 775 } else { 776 // Assuming the bounding geometry is tight so no need to check y values 777 fsBuilder->codeAppendf("\t\tfloat alpha = 1.0;\n"); 778 fsBuilder->codeAppendf("\t\talpha *= (fragPosShifted.x - %s.x) > -0.5 ? 1.0 : 0.0;\n", rectName); 779 fsBuilder->codeAppendf("\t\talpha *= (%s.z - fragPosShifted.x) >= -0.5 ? 1.0 : 0.0;\n", rectName); 780 } 781 fsBuilder->codeAppendf("\t\t%s = %s;\n", args.fOutput, 782 (GrGLSLExpr4(args.fInput) * GrGLSLExpr1("alpha")).c_str()); 783} 784 785void GLDashingLineEffect::setData(const GrGLProgramDataManager& pdman, 786 const GrProcessor& processor) { 787 const DashingLineEffect& de = processor.cast<DashingLineEffect>(); 788 const SkRect& rect = de.getRect(); 789 SkScalar intervalLength = de.getIntervalLength(); 790 if (rect != fPrevRect || intervalLength != fPrevIntervalLength) { 791 pdman.set4f(fRectUniform, rect.fLeft + 0.5f, rect.fTop + 0.5f, 792 rect.fRight - 0.5f, rect.fBottom - 0.5f); 793 pdman.set1f(fIntervalUniform, intervalLength); 794 fPrevRect = rect; 795 fPrevIntervalLength = intervalLength; 796 } 797} 798 799void GLDashingLineEffect::GenKey(const GrProcessor& processor, const GrGLCaps&, 800 GrProcessorKeyBuilder* b) { 801 const DashingLineEffect& de = processor.cast<DashingLineEffect>(); 802 b->add32(de.getEdgeType()); 803} 804 805////////////////////////////////////////////////////////////////////////////// 806 807GrGeometryProcessor* DashingLineEffect::Create(GrPrimitiveEdgeType edgeType, 808 const DashInfo& info, 809 SkScalar strokeWidth) { 810 if (info.fCount != 2) { 811 return NULL; 812 } 813 814 return SkNEW_ARGS(DashingLineEffect, (edgeType, info, strokeWidth)); 815} 816 817DashingLineEffect::~DashingLineEffect() {} 818 819void DashingLineEffect::onComputeInvariantOutput(GrInvariantOutput* inout) const { 820 inout->mulByUnknownAlpha(); 821} 822 823const GrBackendGeometryProcessorFactory& DashingLineEffect::getFactory() const { 824 return GrTBackendGeometryProcessorFactory<DashingLineEffect>::getInstance(); 825} 826 827DashingLineEffect::DashingLineEffect(GrPrimitiveEdgeType edgeType, const DashInfo& info, 828 SkScalar strokeWidth) 829 : fEdgeType(edgeType) 830 , fInCoord(this->addVertexAttrib(GrShaderVar("inCoord", 831 kVec2f_GrSLType, 832 GrShaderVar::kAttribute_TypeModifier))) { 833 SkScalar onLen = info.fIntervals[0]; 834 SkScalar offLen = info.fIntervals[1]; 835 SkScalar halfOffLen = SkScalarHalf(offLen); 836 SkScalar halfStroke = SkScalarHalf(strokeWidth); 837 fIntervalLength = onLen + offLen; 838 fRect.set(halfOffLen, -halfStroke, halfOffLen + onLen, halfStroke); 839} 840 841bool DashingLineEffect::onIsEqual(const GrGeometryProcessor& other) const { 842 const DashingLineEffect& de = other.cast<DashingLineEffect>(); 843 return (fEdgeType == de.fEdgeType && 844 fRect == de.fRect && 845 fIntervalLength == de.fIntervalLength); 846} 847 848GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DashingLineEffect); 849 850GrGeometryProcessor* DashingLineEffect::TestCreate(SkRandom* random, 851 GrContext*, 852 const GrDrawTargetCaps& caps, 853 GrTexture*[]) { 854 GrPrimitiveEdgeType edgeType = static_cast<GrPrimitiveEdgeType>(random->nextULessThan( 855 kGrProcessorEdgeTypeCnt)); 856 SkScalar strokeWidth = random->nextRangeScalar(0, 100.f); 857 DashInfo info; 858 info.fCount = 2; 859 SkAutoTArray<SkScalar> intervals(info.fCount); 860 info.fIntervals = intervals.get(); 861 info.fIntervals[0] = random->nextRangeScalar(0, 10.f); 862 info.fIntervals[1] = random->nextRangeScalar(0, 10.f); 863 info.fPhase = random->nextRangeScalar(0, info.fIntervals[0] + info.fIntervals[1]); 864 865 return DashingLineEffect::Create(edgeType, info, strokeWidth); 866} 867 868////////////////////////////////////////////////////////////////////////////// 869 870GrGeometryProcessor* GrDashingEffect::Create(GrPrimitiveEdgeType edgeType, 871 const SkPathEffect::DashInfo& info, 872 SkScalar strokeWidth, 873 GrDashingEffect::DashCap cap) { 874 switch (cap) { 875 case GrDashingEffect::kRound_DashCap: 876 return DashingCircleEffect::Create(edgeType, info, SkScalarHalf(strokeWidth)); 877 case GrDashingEffect::kNonRound_DashCap: 878 return DashingLineEffect::Create(edgeType, info, strokeWidth); 879 default: 880 SkFAIL("Unexpected dashed cap."); 881 } 882 return NULL; 883} 884