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