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