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