1/*
2 * Copyright 2018 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 "src/gpu/ccpr/GrCCStroker.h"
9
10#include "include/core/SkStrokeRec.h"
11#include "src/core/SkPathPriv.h"
12#include "src/gpu/GrOnFlushResourceProvider.h"
13#include "src/gpu/GrOpsRenderPass.h"
14#include "src/gpu/GrProgramInfo.h"
15#include "src/gpu/ccpr/GrAutoMapVertexBuffer.h"
16#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
17#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
18#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
19
20static constexpr int kMaxNumLinearSegmentsLog2 = GrCCStrokeGeometry::kMaxNumLinearSegmentsLog2;
21using TriangleInstance = GrCCCoverageProcessor::TriPointInstance;
22using ConicInstance = GrCCCoverageProcessor::QuadPointInstance;
23
24namespace {
25
26struct LinearStrokeInstance {
27 float fEndpoints[4];
28 float fStrokeRadius;
29
30 inline void set(const SkPoint[2], float dx, float dy, float strokeRadius);
31};
32
33inline void LinearStrokeInstance::set(const SkPoint P[2], float dx, float dy, float strokeRadius) {
34 Sk2f X, Y;
35 Sk2f::Load2(P, &X, &Y);
36 Sk2f::Store2(fEndpoints, X + dx, Y + dy);
37 fStrokeRadius = strokeRadius;
38}
39
40struct CubicStrokeInstance {
41 float fX[4];
42 float fY[4];
43 float fStrokeRadius;
44 float fNumSegments;
45
46 inline void set(const SkPoint[4], float dx, float dy, float strokeRadius, int numSegments);
47 inline void set(const Sk4f& X, const Sk4f& Y, float dx, float dy, float strokeRadius,
48 int numSegments);
49};
50
51inline void CubicStrokeInstance::set(const SkPoint P[4], float dx, float dy, float strokeRadius,
52 int numSegments) {
53 Sk4f X, Y;
54 Sk4f::Load2(P, &X, &Y);
55 this->set(X, Y, dx, dy, strokeRadius, numSegments);
56}
57
58inline void CubicStrokeInstance::set(const Sk4f& X, const Sk4f& Y, float dx, float dy,
59 float strokeRadius, int numSegments) {
60 (X + dx).store(&fX);
61 (Y + dy).store(&fY);
62 fStrokeRadius = strokeRadius;
63 fNumSegments = static_cast<float>(numSegments);
64}
65
66// This class draws stroked lines in post-transform device space (a.k.a. rectangles). Rigid-body
67// transforms can be achieved by transforming the line ahead of time and adjusting the stroke
68// width. Skews of the stroke itself are not yet supported.
69//
70// Corner coverage is AA-correct, meaning, n^2 attenuation along the diagonals. This is important
71// for seamless integration with the connecting geometry.
72class LinearStrokeProcessor : public GrGeometryProcessor {
73public:
74 LinearStrokeProcessor() : INHERITED(kLinearStrokeProcessor_ClassID) {
75 this->setInstanceAttributes(kInstanceAttribs, 2);
76#ifdef SK_DEBUG
77 using Instance = LinearStrokeInstance;
78 SkASSERT(this->instanceStride() == sizeof(Instance));
79#endif
80 }
81
82private:
83 const char* name() const override { return "LinearStrokeProcessor"; }
84 void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
85
86 static constexpr Attribute kInstanceAttribs[2] = {
87 {"endpts", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
88 {"stroke_radius", kFloat_GrVertexAttribType, kFloat_GrSLType}
89 };
90
91 class Impl : public GrGLSLGeometryProcessor {
92 void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&) override {}
93 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
94 };
95
96 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
97 return new Impl();
98 }
99
100 typedef GrGeometryProcessor INHERITED;
101};
102
103void LinearStrokeProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
104 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
105
106 varyingHandler->emitAttributes(args.fGP.cast<LinearStrokeProcessor>());
107
108 GrGLSLVertexBuilder* v = args.fVertBuilder;
109 v->codeAppend ("float2 tan = normalize(endpts.zw - endpts.xy);");
110 v->codeAppend ("float2 n = float2(tan.y, -tan.x);");
111 v->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
112
113 // Outset the vertex position for AA butt caps.
114 v->codeAppend ("float2 outset = tan*nwidth/2;");
115 v->codeAppend ("float2 position = (sk_VertexID < 2) "
116 "? endpts.xy - outset : endpts.zw + outset;");
117
118 // Calculate Manhattan distance from both butt caps, where distance=0 on the actual endpoint and
119 // distance=-.5 on the outset edge.
120 GrGLSLVarying edgeDistances(kFloat4_GrSLType);
121 varyingHandler->addVarying("edge_distances", &edgeDistances);
122 v->codeAppendf("%s.xz = float2(-.5, dot(endpts.zw - endpts.xy, tan) / nwidth + .5);",
123 edgeDistances.vsOut());
124 v->codeAppendf("%s.xz = (sk_VertexID < 2) ? %s.xz : %s.zx;",
125 edgeDistances.vsOut(), edgeDistances.vsOut(), edgeDistances.vsOut());
126
127 // Outset the vertex position for stroke radius plus edge AA.
128 v->codeAppend ("outset = n * (stroke_radius + nwidth/2);");
129 v->codeAppend ("position += (0 == (sk_VertexID & 1)) ? +outset : -outset;");
130
131 // Calculate Manhattan distance from both edges, where distance=0 on the actual edge and
132 // distance=-.5 on the outset.
133 v->codeAppendf("%s.yw = float2(-.5, 2*stroke_radius / nwidth + .5);", edgeDistances.vsOut());
134 v->codeAppendf("%s.yw = (0 == (sk_VertexID & 1)) ? %s.yw : %s.wy;",
135 edgeDistances.vsOut(), edgeDistances.vsOut(), edgeDistances.vsOut());
136
137 gpArgs->fPositionVar.set(kFloat2_GrSLType, "position");
138 // Leave fLocalCoordVar uninitialized; this GP is not combined with frag processors
139
140 // Use the 4 edge distances to calculate coverage in the fragment shader.
141 GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
142 f->codeAppendf("half2 coverages = half2(min(%s.xy, .5) + min(%s.zw, .5));",
143 edgeDistances.fsIn(), edgeDistances.fsIn());
144 f->codeAppendf("%s = half4(coverages.x * coverages.y);", args.fOutputColor);
145
146 // This shader doesn't use the built-in Ganesh coverage.
147 f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
148}
149
150constexpr GrPrimitiveProcessor::Attribute LinearStrokeProcessor::kInstanceAttribs[];
151
152// This class draws stroked cubics in post-transform device space. Rigid-body transforms can be
153// achieved by transforming the curve ahead of time and adjusting the stroke width. Skews of the
154// stroke itself are not yet supported. Quadratics can be drawn by converting them to cubics.
155//
156// This class works by finding stroke-width line segments orthogonal to the curve at a
157// pre-determined number of evenly spaced points along the curve (evenly spaced in the parametric
158// sense). It then connects the segments with a triangle strip. As for common in CCPR, clockwise-
159// winding triangles from the strip emit positive coverage, counter-clockwise triangles emit
160// negative, and we use SkBlendMode::kPlus.
161class CubicStrokeProcessor : public GrGeometryProcessor {
162public:
163 CubicStrokeProcessor() : GrGeometryProcessor(kCubicStrokeProcessor_ClassID) {
164 this->setInstanceAttributes(kInstanceAttribs, 3);
165#ifdef SK_DEBUG
166 using Instance = CubicStrokeInstance;
167 SkASSERT(this->instanceStride() == sizeof(Instance));
168#endif
169 }
170
171private:
172 const char* name() const override { return "CubicStrokeProcessor"; }
173 void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
174
175 static constexpr Attribute kInstanceAttribs[3] = {
176 {"X", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
177 {"Y", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
178 {"stroke_info", kFloat2_GrVertexAttribType, kFloat2_GrSLType}
179 };
180
181 class Impl : public GrGLSLGeometryProcessor {
182 void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&) override {}
183 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
184 };
185
186 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
187 return new Impl();
188 }
189};
190
191void CubicStrokeProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
192 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
193
194 varyingHandler->emitAttributes(args.fGP.cast<CubicStrokeProcessor>());
195
196 GrGLSLVertexBuilder* v = args.fVertBuilder;
197 v->codeAppend ("float4x2 P = transpose(float2x4(X, Y));");
198 v->codeAppend ("float stroke_radius = stroke_info[0];");
199 v->codeAppend ("float num_segments = stroke_info[1];");
200
201 // Find the parametric T value at which we will emit our orthogonal line segment. We emit two
202 // line segments at T=0 and double at T=1 as well for AA butt caps.
203 v->codeAppend ("float point_id = float(sk_VertexID/2);");
204 v->codeAppend ("float T = max((point_id - 1) / num_segments, 0);");
205 v->codeAppend ("T = (point_id >= num_segments + 1) ? 1 : T;"); // In case x/x !== 1.
206
207 // Use De Casteljau's algorithm to find the position and tangent for our orthogonal line
208 // segment. De Casteljau's is more numerically stable than evaluating the curve and derivative
209 // directly.
210 v->codeAppend ("float2 ab = mix(P[0], P[1], T);");
211 v->codeAppend ("float2 bc = mix(P[1], P[2], T);");
212 v->codeAppend ("float2 cd = mix(P[2], P[3], T);");
213 v->codeAppend ("float2 abc = mix(ab, bc, T);");
214 v->codeAppend ("float2 bcd = mix(bc, cd, T);");
215 v->codeAppend ("float2 position = mix(abc, bcd, T);");
216 v->codeAppend ("float2 tan = bcd - abc;");
217
218 // Find actual tangents for the corner cases when De Casteljau's yields tan=0. (We shouldn't
219 // encounter other numerically unstable cases where tan ~= 0, because GrCCStrokeGeometry snaps
220 // control points to endpoints in curves where they are almost equal.)
221 v->codeAppend ("if (0 == T && P[0] == P[1]) {");
222 v->codeAppend ( "tan = P[2] - P[0];");
223 v->codeAppend ("}");
224 v->codeAppend ("if (1 == T && P[2] == P[3]) {");
225 v->codeAppend ( "tan = P[3] - P[1];");
226 v->codeAppend ("}");
227 v->codeAppend ("tan = normalize(tan);");
228 v->codeAppend ("float2 n = float2(tan.y, -tan.x);");
229 v->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
230
231 // Outset the vertex position for stroke radius plus edge AA.
232 v->codeAppend ("float2 outset = n * (stroke_radius + nwidth/2);");
233 v->codeAppend ("position += (0 == (sk_VertexID & 1)) ? -outset : +outset;");
234
235 // Calculate the Manhattan distance from both edges, where distance=0 on the actual edge and
236 // distance=-.5 on the outset.
237 GrGLSLVarying coverages(kFloat3_GrSLType);
238 varyingHandler->addVarying("coverages", &coverages);
239 v->codeAppendf("%s.xy = float2(-.5, 2*stroke_radius / nwidth + .5);", coverages.vsOut());
240 v->codeAppendf("%s.xy = (0 == (sk_VertexID & 1)) ? %s.xy : %s.yx;",
241 coverages.vsOut(), coverages.vsOut(), coverages.vsOut());
242
243 // Adjust the orthogonal line segments on the endpoints so they straddle the actual endpoint
244 // at a Manhattan distance of .5 on either side.
245 v->codeAppend ("if (0 == point_id || num_segments+1 == point_id) {");
246 v->codeAppend ( "position -= tan*nwidth/2;");
247 v->codeAppend ("}");
248 v->codeAppend ("if (1 == point_id || num_segments+2 == point_id) {");
249 v->codeAppend ( "position += tan*nwidth/2;");
250 v->codeAppend ("}");
251
252 // Interpolate coverage for butt cap AA from 0 on the outer segment to 1 on the inner.
253 v->codeAppendf("%s.z = (0 == point_id || num_segments+2 == point_id) ? 0 : 1;",
254 coverages.vsOut());
255
256 gpArgs->fPositionVar.set(kFloat2_GrSLType, "position");
257 // Leave fLocalCoordVar uninitialized; this GP is not combined with frag processors
258
259 // Use the 2 edge distances and interpolated butt cap AA to calculate fragment coverage.
260 GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
261 f->codeAppendf("half2 edge_coverages = min(half2(%s.xy), .5);", coverages.fsIn());
262 f->codeAppend ("half coverage = edge_coverages.x + edge_coverages.y;");
263 f->codeAppendf("coverage *= half(%s.z);", coverages.fsIn()); // Butt cap AA.
264
265 // As is common for CCPR, clockwise-winding triangles from the strip emit positive coverage, and
266 // counter-clockwise triangles emit negative.
267 f->codeAppendf("%s = half4(sk_Clockwise ? +coverage : -coverage);", args.fOutputColor);
268
269 // This shader doesn't use the built-in Ganesh coverage.
270 f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
271}
272
273constexpr GrPrimitiveProcessor::Attribute CubicStrokeProcessor::kInstanceAttribs[];
274
275} // anonymous namespace
276
277void GrCCStroker::parseDeviceSpaceStroke(const SkPath& path, const SkPoint* deviceSpacePts,
278 const SkStrokeRec& stroke, float strokeDevWidth,
279 GrScissorTest scissorTest,
280 const SkIRect& clippedDevIBounds,
281 const SkIVector& devToAtlasOffset) {
282 SkASSERT(SkStrokeRec::kStroke_Style == stroke.getStyle() ||
283 SkStrokeRec::kHairline_Style == stroke.getStyle());
284 SkASSERT(!fInstanceBuffer);
285 SkASSERT(!path.isEmpty());
286
287 if (!fHasOpenBatch) {
288 fBatches.emplace_back(&fTalliesAllocator, *fInstanceCounts[(int)GrScissorTest::kDisabled],
289 fScissorSubBatches.count());
290 fInstanceCounts[(int)GrScissorTest::kDisabled] = fBatches.back().fNonScissorEndInstances;
291 fHasOpenBatch = true;
292 }
293
294 InstanceTallies* currStrokeEndIndices;
295 if (GrScissorTest::kEnabled == scissorTest) {
296 SkASSERT(fBatches.back().fEndScissorSubBatch == fScissorSubBatches.count());
297 fScissorSubBatches.emplace_back(&fTalliesAllocator,
298 *fInstanceCounts[(int)GrScissorTest::kEnabled],
299 clippedDevIBounds.makeOffset(devToAtlasOffset));
300 fBatches.back().fEndScissorSubBatch = fScissorSubBatches.count();
301 fInstanceCounts[(int)GrScissorTest::kEnabled] =
302 currStrokeEndIndices = fScissorSubBatches.back().fEndInstances;
303 } else {
304 currStrokeEndIndices = fBatches.back().fNonScissorEndInstances;
305 }
306
307 fGeometry.beginPath(stroke, strokeDevWidth, currStrokeEndIndices);
308
309 fPathInfos.push_back() = {devToAtlasOffset, strokeDevWidth/2, scissorTest};
310
311 int devPtsIdx = 0;
312 SkPath::Verb previousVerb = SkPath::kClose_Verb;
313
314 for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
315 SkASSERT(SkPath::kDone_Verb != previousVerb);
316 const SkPoint* P = &deviceSpacePts[devPtsIdx - 1];
317 switch (verb) {
318 case SkPath::kMove_Verb:
319 if (devPtsIdx > 0 && SkPath::kClose_Verb != previousVerb) {
320 fGeometry.capContourAndExit();
321 }
322 fGeometry.moveTo(deviceSpacePts[devPtsIdx]);
323 ++devPtsIdx;
324 break;
325 case SkPath::kClose_Verb:
326 SkASSERT(SkPath::kClose_Verb != previousVerb);
327 fGeometry.closeContour();
328 break;
329 case SkPath::kLine_Verb:
330 SkASSERT(SkPath::kClose_Verb != previousVerb);
331 fGeometry.lineTo(P[1]);
332 ++devPtsIdx;
333 break;
334 case SkPath::kQuad_Verb:
335 SkASSERT(SkPath::kClose_Verb != previousVerb);
336 fGeometry.quadraticTo(P);
337 devPtsIdx += 2;
338 break;
339 case SkPath::kCubic_Verb: {
340 SkASSERT(SkPath::kClose_Verb != previousVerb);
341 fGeometry.cubicTo(P);
342 devPtsIdx += 3;
343 break;
344 }
345 case SkPath::kConic_Verb:
346 SkASSERT(SkPath::kClose_Verb != previousVerb);
347 SK_ABORT("Stroked conics not supported.");
348 break;
349 case SkPath::kDone_Verb:
350 break;
351 }
352 previousVerb = verb;
353 }
354
355 if (devPtsIdx > 0 && SkPath::kClose_Verb != previousVerb) {
356 fGeometry.capContourAndExit();
357 }
358}
359
360// This class encapsulates the process of expanding ready-to-draw geometry from GrCCStrokeGeometry
361// directly into GPU instance buffers.
362class GrCCStroker::InstanceBufferBuilder {
363public:
364 InstanceBufferBuilder(GrOnFlushResourceProvider* onFlushRP, GrCCStroker* stroker) {
365 memcpy(fNextInstances, stroker->fBaseInstances, sizeof(fNextInstances));
366#ifdef SK_DEBUG
367 fEndInstances[0] = stroker->fBaseInstances[0] + *stroker->fInstanceCounts[0];
368 fEndInstances[1] = stroker->fBaseInstances[1] + *stroker->fInstanceCounts[1];
369#endif
370
371 int endConicsIdx = stroker->fBaseInstances[1].fConics +
372 stroker->fInstanceCounts[1]->fConics;
373 fInstanceBuffer.resetAndMapBuffer(onFlushRP, endConicsIdx * sizeof(ConicInstance));
374 if (!fInstanceBuffer.hasGpuBuffer()) {
375 SkDebugf("WARNING: failed to allocate CCPR stroke instance buffer.\n");
376 return;
377 }
378 }
379
380 bool isMapped() const { return fInstanceBuffer.isMapped(); }
381
382 void updateCurrentInfo(const PathInfo& pathInfo) {
383 SkASSERT(this->isMapped());
384 fCurrDX = static_cast<float>(pathInfo.fDevToAtlasOffset.x());
385 fCurrDY = static_cast<float>(pathInfo.fDevToAtlasOffset.y());
386 fCurrStrokeRadius = pathInfo.fStrokeRadius;
387 fCurrNextInstances = &fNextInstances[(int)pathInfo.fScissorTest];
388 SkDEBUGCODE(fCurrEndInstances = &fEndInstances[(int)pathInfo.fScissorTest]);
389 }
390
391 void appendLinearStroke(const SkPoint endpts[2]) {
392 SkASSERT(this->isMapped());
393 this->appendLinearStrokeInstance().set(endpts, fCurrDX, fCurrDY, fCurrStrokeRadius);
394 }
395
396 void appendQuadraticStroke(const SkPoint P[3], int numLinearSegmentsLog2) {
397 SkASSERT(this->isMapped());
398 SkASSERT(numLinearSegmentsLog2 > 0);
399
400 Sk4f ptsT[2];
401 Sk2f p0 = Sk2f::Load(P);
402 Sk2f p1 = Sk2f::Load(P+1);
403 Sk2f p2 = Sk2f::Load(P+2);
404
405 // Convert the quadratic to cubic.
406 Sk2f c1 = SkNx_fma(Sk2f(2/3.f), p1 - p0, p0);
407 Sk2f c2 = SkNx_fma(Sk2f(1/3.f), p2 - p1, p1);
408 Sk2f::Store4(ptsT, p0, c1, c2, p2);
409
410 this->appendCubicStrokeInstance(numLinearSegmentsLog2).set(
411 ptsT[0], ptsT[1], fCurrDX, fCurrDY, fCurrStrokeRadius, 1 << numLinearSegmentsLog2);
412 }
413
414 void appendCubicStroke(const SkPoint P[3], int numLinearSegmentsLog2) {
415 SkASSERT(this->isMapped());
416 SkASSERT(numLinearSegmentsLog2 > 0);
417 this->appendCubicStrokeInstance(numLinearSegmentsLog2).set(
418 P, fCurrDX, fCurrDY, fCurrStrokeRadius, 1 << numLinearSegmentsLog2);
419 }
420
421 void appendJoin(Verb joinVerb, const SkPoint& center, const SkVector& leftNorm,
422 const SkVector& rightNorm, float miterCapHeightOverWidth, float conicWeight) {
423 SkASSERT(this->isMapped());
424
425 Sk2f offset = Sk2f::Load(&center) + Sk2f(fCurrDX, fCurrDY);
426 Sk2f n0 = Sk2f::Load(&leftNorm);
427 Sk2f n1 = Sk2f::Load(&rightNorm);
428
429 // Identify the outer edge.
430 Sk2f cross = n0 * SkNx_shuffle<1,0>(n1);
431 if (cross[0] < cross[1]) {
432 Sk2f tmp = n0;
433 n0 = -n1;
434 n1 = -tmp;
435 }
436
437 if (!GrCCStrokeGeometry::IsInternalJoinVerb(joinVerb)) {
438 // Normal joins are a triangle that connects the outer corners of two adjoining strokes.
439 this->appendTriangleInstance().set(
440 n1 * fCurrStrokeRadius, Sk2f(0, 0), n0 * fCurrStrokeRadius, offset,
441 TriangleInstance::Ordering::kXYTransposed);
442 if (Verb::kBevelJoin == joinVerb) {
443 return;
444 }
445 } else {
446 // Internal joins are coverage-counted, self-intersecting quadrilaterals that tie the
447 // four corners of two adjoining strokes together a like a shoelace. Coverage is
448 // negative on the inside half. We implement this geometry with a pair of triangles.
449 this->appendTriangleInstance().set(
450 -n0 * fCurrStrokeRadius, n0 * fCurrStrokeRadius, n1 * fCurrStrokeRadius,
451 offset, TriangleInstance::Ordering::kXYTransposed);
452 if (Verb::kBevelJoin == joinVerb) {
453 return;
454 }
455 this->appendTriangleInstance().set(
456 -n0 * fCurrStrokeRadius, n1 * fCurrStrokeRadius, -n1 * fCurrStrokeRadius,
457 offset, TriangleInstance::Ordering::kXYTransposed);
458 if (Verb::kBevelJoin == joinVerb) {
459 return;
460 }
461 if (Verb::kInternalBevelJoin == joinVerb) {
462 return;
463 }
464 }
465
466 // For miter and round joins, we place an additional triangle cap on top of the bevel. This
467 // triangle is literal for miters and is conic control points for round joins.
468 SkASSERT(miterCapHeightOverWidth >= 0 || SkScalarIsNaN(miterCapHeightOverWidth));
469 Sk2f base = n1 - n0;
470 Sk2f baseNorm = Sk2f(base[1], -base[0]);
471 Sk2f c = (n0 + n1) * .5f + baseNorm * miterCapHeightOverWidth;
472
473 if (Verb::kMiterJoin == joinVerb) {
474 this->appendTriangleInstance().set(
475 n0 * fCurrStrokeRadius, c * fCurrStrokeRadius, n1 * fCurrStrokeRadius, offset,
476 TriangleInstance::Ordering::kXYTransposed);
477 } else {
478 SkASSERT(Verb::kRoundJoin == joinVerb || Verb::kInternalRoundJoin == joinVerb);
479 this->appendConicInstance().setW(n0 * fCurrStrokeRadius, c * fCurrStrokeRadius,
480 n1 * fCurrStrokeRadius, offset, conicWeight);
481 if (Verb::kInternalRoundJoin == joinVerb) {
482 this->appendConicInstance().setW(-n1 * fCurrStrokeRadius, c * -fCurrStrokeRadius,
483 -n0 * fCurrStrokeRadius, offset, conicWeight);
484 }
485 }
486 }
487
488 void appendCap(Verb capType, const SkPoint& pt, const SkVector& norm) {
489 SkASSERT(this->isMapped());
490
491 Sk2f n = Sk2f::Load(&norm) * fCurrStrokeRadius;
492 Sk2f v = Sk2f(-n[1], n[0]);
493 Sk2f offset = Sk2f::Load(&pt) + Sk2f(fCurrDX, fCurrDY);
494
495 if (Verb::kSquareCap == capType) {
496 SkPoint endPts[2] = {{0, 0}, {v[0], v[1]}};
497 this->appendLinearStrokeInstance().set(endPts, offset[0], offset[1], fCurrStrokeRadius);
498 } else {
499 SkASSERT(Verb::kRoundCap == capType);
500 this->appendTriangleInstance().set(
501 n, v, -n, offset, TriangleInstance::Ordering::kXYTransposed);
502 this->appendConicInstance().setW(n, n + v, v, offset, SK_ScalarRoot2Over2);
503 this->appendConicInstance().setW(v, v - n, -n, offset, SK_ScalarRoot2Over2);
504 }
505 }
506
507 sk_sp<const GrGpuBuffer> finish() {
508 SkASSERT(this->isMapped());
509 SkASSERT(!memcmp(fNextInstances, fEndInstances, sizeof(fNextInstances)));
510 fInstanceBuffer.unmapBuffer();
511 SkASSERT(!this->isMapped());
512 return fInstanceBuffer.gpuBuffer();
513 }
514
515private:
516 LinearStrokeInstance& appendLinearStrokeInstance() {
517 int instanceIdx = fCurrNextInstances->fStrokes[0]++;
518 SkASSERT(instanceIdx < fCurrEndInstances->fStrokes[0]);
519
520 return reinterpret_cast<LinearStrokeInstance*>(fInstanceBuffer.data())[instanceIdx];
521 }
522
523 CubicStrokeInstance& appendCubicStrokeInstance(int numLinearSegmentsLog2) {
524 SkASSERT(numLinearSegmentsLog2 > 0);
525 SkASSERT(numLinearSegmentsLog2 <= kMaxNumLinearSegmentsLog2);
526
527 int instanceIdx = fCurrNextInstances->fStrokes[numLinearSegmentsLog2]++;
528 SkASSERT(instanceIdx < fCurrEndInstances->fStrokes[numLinearSegmentsLog2]);
529
530 return reinterpret_cast<CubicStrokeInstance*>(fInstanceBuffer.data())[instanceIdx];
531 }
532
533 TriangleInstance& appendTriangleInstance() {
534 int instanceIdx = fCurrNextInstances->fTriangles++;
535 SkASSERT(instanceIdx < fCurrEndInstances->fTriangles);
536
537 return reinterpret_cast<TriangleInstance*>(fInstanceBuffer.data())[instanceIdx];
538 }
539
540 ConicInstance& appendConicInstance() {
541 int instanceIdx = fCurrNextInstances->fConics++;
542 SkASSERT(instanceIdx < fCurrEndInstances->fConics);
543
544 return reinterpret_cast<ConicInstance*>(fInstanceBuffer.data())[instanceIdx];
545 }
546
547 float fCurrDX, fCurrDY;
548 float fCurrStrokeRadius;
549 InstanceTallies* fCurrNextInstances;
550 SkDEBUGCODE(const InstanceTallies* fCurrEndInstances);
551
552 GrAutoMapVertexBuffer fInstanceBuffer;
553 InstanceTallies fNextInstances[2];
554 SkDEBUGCODE(InstanceTallies fEndInstances[2]);
555};
556
557GrCCStroker::BatchID GrCCStroker::closeCurrentBatch() {
558 if (!fHasOpenBatch) {
559 return kEmptyBatchID;
560 }
561 int start = (fBatches.count() < 2) ? 0 : fBatches[fBatches.count() - 2].fEndScissorSubBatch;
562 int end = fBatches.back().fEndScissorSubBatch;
563 fMaxNumScissorSubBatches = std::max(fMaxNumScissorSubBatches, end - start);
564 fHasOpenBatch = false;
565 return fBatches.count() - 1;
566}
567
568bool GrCCStroker::prepareToDraw(GrOnFlushResourceProvider* onFlushRP) {
569 SkASSERT(!fInstanceBuffer);
570 SkASSERT(!fHasOpenBatch); // Call closeCurrentBatch() first.
571
572 // Here we layout a single instance buffer to share with every internal batch.
573 //
574 // Rather than place each instance array in its own GPU buffer, we allocate a single
575 // megabuffer and lay them all out side-by-side. We can offset the "baseInstance" parameter in
576 // our draw calls to direct the GPU to the applicable elements within a given array.
577 fBaseInstances[0].fStrokes[0] = 0;
578 fBaseInstances[1].fStrokes[0] = fInstanceCounts[0]->fStrokes[0];
579 int endLinearStrokesIdx = fBaseInstances[1].fStrokes[0] + fInstanceCounts[1]->fStrokes[0];
580
581 int cubicStrokesIdx = GrSizeDivRoundUp(endLinearStrokesIdx * sizeof(LinearStrokeInstance),
582 sizeof(CubicStrokeInstance));
583 for (int i = 1; i <= kMaxNumLinearSegmentsLog2; ++i) {
584 for (int j = 0; j < kNumScissorModes; ++j) {
585 fBaseInstances[j].fStrokes[i] = cubicStrokesIdx;
586 cubicStrokesIdx += fInstanceCounts[j]->fStrokes[i];
587 }
588 }
589
590 int trianglesIdx = GrSizeDivRoundUp(cubicStrokesIdx * sizeof(CubicStrokeInstance),
591 sizeof(TriangleInstance));
592 fBaseInstances[0].fTriangles = trianglesIdx;
593 fBaseInstances[1].fTriangles =
594 fBaseInstances[0].fTriangles + fInstanceCounts[0]->fTriangles;
595 int endTrianglesIdx =
596 fBaseInstances[1].fTriangles + fInstanceCounts[1]->fTriangles;
597
598 int conicsIdx =
599 GrSizeDivRoundUp(endTrianglesIdx * sizeof(TriangleInstance), sizeof(ConicInstance));
600 fBaseInstances[0].fConics = conicsIdx;
601 fBaseInstances[1].fConics = fBaseInstances[0].fConics + fInstanceCounts[0]->fConics;
602
603 InstanceBufferBuilder builder(onFlushRP, this);
604 if (!builder.isMapped()) {
605 return false; // Buffer allocation failed.
606 }
607
608 // Now parse the GrCCStrokeGeometry and expand it into the instance buffer.
609 int pathIdx = 0;
610 int ptsIdx = 0;
611 int paramsIdx = 0;
612 int normalsIdx = 0;
613
614 const SkTArray<GrCCStrokeGeometry::Parameter, true>& params = fGeometry.params();
615 const SkTArray<SkPoint, true>& pts = fGeometry.points();
616 const SkTArray<SkVector, true>& normals = fGeometry.normals();
617
618 float miterCapHeightOverWidth=0, conicWeight=0;
619
620 for (Verb verb : fGeometry.verbs()) {
621 switch (verb) {
622 case Verb::kBeginPath:
623 builder.updateCurrentInfo(fPathInfos[pathIdx]);
624 ++pathIdx;
625 continue;
626
627 case Verb::kLinearStroke:
628 builder.appendLinearStroke(&pts[ptsIdx]);
629 ++ptsIdx;
630 continue;
631 case Verb::kQuadraticStroke:
632 builder.appendQuadraticStroke(&pts[ptsIdx],
633 params[paramsIdx++].fNumLinearSegmentsLog2);
634 ptsIdx += 2;
635 ++normalsIdx;
636 continue;
637 case Verb::kCubicStroke:
638 builder.appendCubicStroke(&pts[ptsIdx], params[paramsIdx++].fNumLinearSegmentsLog2);
639 ptsIdx += 3;
640 ++normalsIdx;
641 continue;
642
643 case Verb::kRoundJoin:
644 case Verb::kInternalRoundJoin:
645 conicWeight = params[paramsIdx++].fConicWeight;
646 [[fallthrough]];
647 case Verb::kMiterJoin:
648 miterCapHeightOverWidth = params[paramsIdx++].fMiterCapHeightOverWidth;
649 [[fallthrough]];
650 case Verb::kBevelJoin:
651 case Verb::kInternalBevelJoin:
652 builder.appendJoin(verb, pts[ptsIdx], normals[normalsIdx], normals[normalsIdx + 1],
653 miterCapHeightOverWidth, conicWeight);
654 ++normalsIdx;
655 continue;
656
657 case Verb::kSquareCap:
658 case Verb::kRoundCap:
659 builder.appendCap(verb, pts[ptsIdx], normals[normalsIdx]);
660 continue;
661
662 case Verb::kEndContour:
663 ++ptsIdx;
664 ++normalsIdx;
665 continue;
666 }
667 SK_ABORT("Invalid CCPR stroke element.");
668 }
669
670 fInstanceBuffer = builder.finish();
671 SkASSERT(fPathInfos.count() == pathIdx);
672 SkASSERT(pts.count() == ptsIdx);
673 SkASSERT(normals.count() == normalsIdx);
674 return true;
675}
676
677void GrCCStroker::drawStrokes(GrOpFlushState* flushState, GrCCCoverageProcessor* proc,
678 BatchID batchID, const SkIRect& drawBounds) const {
679 using PrimitiveType = GrCCCoverageProcessor::PrimitiveType;
680 SkASSERT(fInstanceBuffer);
681
682 if (kEmptyBatchID == batchID) {
683 return;
684 }
685 const Batch& batch = fBatches[batchID];
686 int startScissorSubBatch = (!batchID) ? 0 : fBatches[batchID - 1].fEndScissorSubBatch;
687
688 const InstanceTallies* startIndices[2];
689 startIndices[(int)GrScissorTest::kDisabled] = (!batchID)
690 ? &fZeroTallies : fBatches[batchID - 1].fNonScissorEndInstances;
691 startIndices[(int)GrScissorTest::kEnabled] = (!startScissorSubBatch)
692 ? &fZeroTallies : fScissorSubBatches[startScissorSubBatch - 1].fEndInstances;
693
694 GrPipeline pipeline(GrScissorTest::kEnabled, SkBlendMode::kPlus,
695 flushState->drawOpArgs().writeSwizzle());
696
697 // Draw linear strokes.
698 this->drawLog2Strokes(0, flushState, LinearStrokeProcessor(), pipeline, batch, startIndices,
699 startScissorSubBatch, drawBounds);
700
701 // Draw cubic strokes. (Quadratics were converted to cubics for GPU processing.)
702 CubicStrokeProcessor cubicProc;
703 for (int i = 1; i <= kMaxNumLinearSegmentsLog2; ++i) {
704 this->drawLog2Strokes(i, flushState, cubicProc, pipeline, batch, startIndices,
705 startScissorSubBatch, drawBounds);
706 }
707
708 int numConnectingGeometrySubpasses = proc->numSubpasses();
709
710 // Draw triangles.
711 for (int i = 0; i < numConnectingGeometrySubpasses; ++i) {
712 proc->reset(PrimitiveType::kTriangles, i, flushState->resourceProvider());
713 this->drawConnectingGeometry<&InstanceTallies::fTriangles>(
714 flushState, pipeline, *proc, batch, startIndices, startScissorSubBatch, drawBounds);
715 }
716
717 // Draw conics.
718 for (int i = 0; i < numConnectingGeometrySubpasses; ++i) {
719 proc->reset(PrimitiveType::kConics, i, flushState->resourceProvider());
720 this->drawConnectingGeometry<&InstanceTallies::fConics>(
721 flushState, pipeline, *proc, batch, startIndices, startScissorSubBatch, drawBounds);
722 }
723}
724
725void GrCCStroker::drawLog2Strokes(int numSegmentsLog2, GrOpFlushState* flushState,
726 const GrPrimitiveProcessor& processor, const GrPipeline& pipeline,
727 const Batch& batch, const InstanceTallies* startIndices[2],
728 int startScissorSubBatch, const SkIRect& drawBounds) const {
729 GrProgramInfo programInfo(flushState->proxy()->numSamples(),
730 flushState->proxy()->numStencilSamples(),
731 flushState->proxy()->backendFormat(),
732 flushState->writeView()->origin(), &pipeline, &processor,
733 GrPrimitiveType::kTriangleStrip);
734
735 flushState->bindPipeline(programInfo, SkRect::Make(drawBounds));
736 flushState->bindBuffers(nullptr, fInstanceBuffer, nullptr);
737
738 // Linear strokes draw a quad. Cubic strokes emit a strip with normals at "numSegments"
739 // evenly-spaced points along the curve, plus one more for the final endpoint, plus two more for
740 // AA butt caps. (i.e., 2 vertices * (numSegments + 3).)
741 int numStripVertices = (0 == numSegmentsLog2) ? 4 : ((1 << numSegmentsLog2) + 3) * 2;
742
743 // Draw non-scissored strokes.
744 int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].fStrokes[numSegmentsLog2];
745 int startIdx = startIndices[(int)GrScissorTest::kDisabled]->fStrokes[numSegmentsLog2];
746 int endIdx = batch.fNonScissorEndInstances->fStrokes[numSegmentsLog2];
747 SkASSERT(endIdx >= startIdx);
748 if (int instanceCount = endIdx - startIdx) {
749 flushState->setScissorRect(drawBounds);
750 flushState->drawInstanced(instanceCount, baseInstance + startIdx, numStripVertices, 0);
751 }
752
753 // Draw scissored strokes.
754 baseInstance = fBaseInstances[(int)GrScissorTest::kEnabled].fStrokes[numSegmentsLog2];
755 startIdx = startIndices[(int)GrScissorTest::kEnabled]->fStrokes[numSegmentsLog2];
756 for (int i = startScissorSubBatch; i < batch.fEndScissorSubBatch; ++i) {
757 const ScissorSubBatch& subBatch = fScissorSubBatches[i];
758 endIdx = subBatch.fEndInstances->fStrokes[numSegmentsLog2];
759 SkASSERT(endIdx >= startIdx);
760 if (int instanceCount = endIdx - startIdx) {
761 flushState->setScissorRect(subBatch.fScissor);
762 flushState->drawInstanced(instanceCount, baseInstance + startIdx, numStripVertices, 0);
763 startIdx = endIdx;
764 }
765 }
766}
767
768template<int GrCCStrokeGeometry::InstanceTallies::* InstanceType>
769void GrCCStroker::drawConnectingGeometry(GrOpFlushState* flushState, const GrPipeline& pipeline,
770 const GrCCCoverageProcessor& processor,
771 const Batch& batch, const InstanceTallies* startIndices[2],
772 int startScissorSubBatch,
773 const SkIRect& drawBounds) const {
774 processor.bindPipeline(flushState, pipeline, SkRect::Make(drawBounds));
775 processor.bindBuffers(flushState->opsRenderPass(), fInstanceBuffer);
776
777 // Append non-scissored meshes.
778 int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].*InstanceType;
779 int startIdx = startIndices[(int)GrScissorTest::kDisabled]->*InstanceType;
780 int endIdx = batch.fNonScissorEndInstances->*InstanceType;
781 SkASSERT(endIdx >= startIdx);
782 if (int instanceCount = endIdx - startIdx) {
783 flushState->setScissorRect(drawBounds);
784 processor.drawInstances(flushState->opsRenderPass(), instanceCount,
785 baseInstance + startIdx);
786 }
787
788 // Append scissored meshes.
789 baseInstance = fBaseInstances[(int)GrScissorTest::kEnabled].*InstanceType;
790 startIdx = startIndices[(int)GrScissorTest::kEnabled]->*InstanceType;
791 for (int i = startScissorSubBatch; i < batch.fEndScissorSubBatch; ++i) {
792 const ScissorSubBatch& subBatch = fScissorSubBatches[i];
793 endIdx = subBatch.fEndInstances->*InstanceType;
794 SkASSERT(endIdx >= startIdx);
795 if (int instanceCount = endIdx - startIdx) {
796 flushState->setScissorRect(subBatch.fScissor);
797 processor.drawInstances(flushState->opsRenderPass(), instanceCount,
798 baseInstance + startIdx);
799 startIdx = endIdx;
800 }
801 }
802}
803