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(&parallelScale, &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                                         &paramName);
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