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