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