xref: /third_party/skia/src/gpu/ops/DashOp.cpp (revision cb93a386)
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