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