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