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 | |
36 | using AAMode = GrDashOp::AAMode; |
37 | |
38 | /////////////////////////////////////////////////////////////////////////////// |
39 | |
40 | // Returns whether or not the gpu can fast path the dash line effect. |
41 | bool 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 | |
79 | static 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 |
102 | static 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 |
120 | static 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 | |
129 | static 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 | |
150 | enum DashCap { |
151 | kRound_DashCap, |
152 | kNonRound_DashCap, |
153 | }; |
154 | |
155 | static 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 | */ |
193 | static GrGeometryProcessor* make_dash_gp(SkArenaAlloc* arena, |
194 | const SkPMColor4f&, |
195 | AAMode aaMode, |
196 | DashCap cap, |
197 | const SkMatrix& localMatrix, |
198 | bool usesLocalCoords); |
199 | |
200 | class DashOp final : public GrMeshDrawOp { |
201 | public: |
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 | |
278 | private: |
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 | |
731 | std::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 | |
793 | class 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 | */ |
804 | class DashingCircleEffect : public GrGeometryProcessor { |
805 | public: |
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 | |
828 | private: |
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 | |
851 | class GLDashingCircleEffect : public GrGLSLGeometryProcessor { |
852 | public: |
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 | |
864 | private: |
865 | UniformHandle fParamUniform; |
866 | UniformHandle fColorUniform; |
867 | SkPMColor4f fColor; |
868 | SkScalar fPrevRadius; |
869 | SkScalar fPrevCenterX; |
870 | SkScalar fPrevIntervalLength; |
871 | typedef GrGLSLGeometryProcessor INHERITED; |
872 | }; |
873 | |
874 | GLDashingCircleEffect::GLDashingCircleEffect() { |
875 | fColor = SK_PMColor4fILLEGAL; |
876 | fPrevRadius = SK_ScalarMin; |
877 | fPrevCenterX = SK_ScalarMin; |
878 | fPrevIntervalLength = SK_ScalarMax; |
879 | } |
880 | |
881 | void 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 | |
934 | void 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 | |
945 | void 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 | |
957 | GrGeometryProcessor* 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 | |
965 | void DashingCircleEffect::getGLSLProcessorKey(const GrShaderCaps& caps, |
966 | GrProcessorKeyBuilder* b) const { |
967 | GLDashingCircleEffect::GenKey(*this, caps, b); |
968 | } |
969 | |
970 | GrGLSLPrimitiveProcessor* DashingCircleEffect::createGLSLInstance(const GrShaderCaps&) const { |
971 | return new GLDashingCircleEffect(); |
972 | } |
973 | |
974 | DashingCircleEffect::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 | |
989 | GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DashingCircleEffect); |
990 | |
991 | #if GR_TEST_UTILS |
992 | GrGeometryProcessor* 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 | |
1003 | class 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 | */ |
1014 | class DashingLineEffect : public GrGeometryProcessor { |
1015 | public: |
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 | |
1038 | private: |
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 | |
1061 | class GLDashingLineEffect : public GrGLSLGeometryProcessor { |
1062 | public: |
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 | |
1074 | private: |
1075 | SkPMColor4f fColor; |
1076 | UniformHandle fColorUniform; |
1077 | typedef GrGLSLGeometryProcessor INHERITED; |
1078 | }; |
1079 | |
1080 | GLDashingLineEffect::GLDashingLineEffect() : fColor(SK_PMColor4fILLEGAL) {} |
1081 | |
1082 | void 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 | |
1161 | void 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 | |
1172 | void 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 | |
1184 | GrGeometryProcessor* 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 | |
1192 | void DashingLineEffect::getGLSLProcessorKey(const GrShaderCaps& caps, |
1193 | GrProcessorKeyBuilder* b) const { |
1194 | GLDashingLineEffect::GenKey(*this, caps, b); |
1195 | } |
1196 | |
1197 | GrGLSLPrimitiveProcessor* DashingLineEffect::createGLSLInstance(const GrShaderCaps&) const { |
1198 | return new GLDashingLineEffect(); |
1199 | } |
1200 | |
1201 | DashingLineEffect::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 | |
1216 | GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DashingLineEffect); |
1217 | |
1218 | #if GR_TEST_UTILS |
1219 | GrGeometryProcessor* 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 | |
1230 | static 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 | |
1255 | GR_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 | |