1 /*
2 * Copyright 2011 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/ops/DefaultPathRenderer.h"
9
10 #include "include/core/SkString.h"
11 #include "include/core/SkStrokeRec.h"
12 #include "src/core/SkGeometry.h"
13 #include "src/core/SkMatrixPriv.h"
14 #include "src/core/SkTLazy.h"
15 #include "src/core/SkTraceEvent.h"
16 #include "src/gpu/GrAuditTrail.h"
17 #include "src/gpu/GrCaps.h"
18 #include "src/gpu/GrClip.h"
19 #include "src/gpu/GrDefaultGeoProcFactory.h"
20 #include "src/gpu/GrDrawOpTest.h"
21 #include "src/gpu/GrOpFlushState.h"
22 #include "src/gpu/GrProgramInfo.h"
23 #include "src/gpu/GrSimpleMesh.h"
24 #include "src/gpu/GrStyle.h"
25 #include "src/gpu/GrUtil.h"
26 #include "src/gpu/effects/GrDisableColorXP.h"
27 #include "src/gpu/geometry/GrPathUtils.h"
28 #include "src/gpu/geometry/GrStyledShape.h"
29 #include "src/gpu/ops/GrMeshDrawOp.h"
30 #include "src/gpu/ops/GrPathStencilSettings.h"
31 #include "src/gpu/ops/GrSimpleMeshDrawOpHelperWithStencil.h"
32 #include "src/gpu/v1/SurfaceDrawContext_v1.h"
33
34 ////////////////////////////////////////////////////////////////////////////////
35 // Helpers for drawPath
36
37 namespace {
38
39 #define STENCIL_OFF 0 // Always disable stencil (even when needed)
40
single_pass_shape(const GrStyledShape & shape)41 inline bool single_pass_shape(const GrStyledShape& shape) {
42 #if STENCIL_OFF
43 return true;
44 #else
45 // Inverse fill is always two pass.
46 if (shape.inverseFilled()) {
47 return false;
48 }
49 // This path renderer only accepts simple fill paths or stroke paths that are either hairline
50 // or have a stroke width small enough to treat as hairline. Hairline paths are always single
51 // pass. Filled paths are single pass if they're convex.
52 if (shape.style().isSimpleFill()) {
53 return shape.knownToBeConvex();
54 }
55 return true;
56 #endif
57 }
58
59 class PathGeoBuilder {
60 public:
PathGeoBuilder(GrPrimitiveType primitiveType,GrMeshDrawTarget * target,SkTDArray<GrSimpleMesh * > * meshes)61 PathGeoBuilder(GrPrimitiveType primitiveType,
62 GrMeshDrawTarget* target,
63 SkTDArray<GrSimpleMesh*>* meshes)
64 : fPrimitiveType(primitiveType)
65 , fTarget(target)
66 , fVertexStride(sizeof(SkPoint))
67 , fFirstIndex(0)
68 , fIndicesInChunk(0)
69 , fIndices(nullptr)
70 , fMeshes(meshes) {
71 this->allocNewBuffers();
72 }
73
~PathGeoBuilder()74 ~PathGeoBuilder() {
75 this->createMeshAndPutBackReserve();
76 }
77
78 /**
79 * Path verbs
80 */
moveTo(const SkPoint & p)81 void moveTo(const SkPoint& p) {
82 if (!this->ensureSpace(1)) {
83 return;
84 }
85
86 if (!this->isHairline()) {
87 fSubpathIndexStart = this->currentIndex();
88 fSubpathStartPoint = p;
89 }
90 *(fCurVert++) = p;
91 }
92
addLine(const SkPoint pts[])93 void addLine(const SkPoint pts[]) {
94 if (!this->ensureSpace(1, this->indexScale(), &pts[0])) {
95 return;
96 }
97
98 if (this->isIndexed()) {
99 uint16_t prevIdx = this->currentIndex() - 1;
100 this->appendCountourEdgeIndices(prevIdx);
101 }
102 *(fCurVert++) = pts[1];
103 }
104
addQuad(const SkPoint pts[],SkScalar srcSpaceTolSqd,SkScalar srcSpaceTol)105 void addQuad(const SkPoint pts[], SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol) {
106 if (!this->ensureSpace(GrPathUtils::kMaxPointsPerCurve,
107 GrPathUtils::kMaxPointsPerCurve * this->indexScale(),
108 &pts[0])) {
109 return;
110 }
111
112 // First pt of quad is the pt we ended on in previous step
113 uint16_t firstQPtIdx = this->currentIndex() - 1;
114 uint16_t numPts = (uint16_t)GrPathUtils::generateQuadraticPoints(
115 pts[0], pts[1], pts[2], srcSpaceTolSqd, &fCurVert,
116 GrPathUtils::quadraticPointCount(pts, srcSpaceTol));
117 if (this->isIndexed()) {
118 for (uint16_t i = 0; i < numPts; ++i) {
119 this->appendCountourEdgeIndices(firstQPtIdx + i);
120 }
121 }
122 }
123
addConic(SkScalar weight,const SkPoint pts[],SkScalar srcSpaceTolSqd,SkScalar srcSpaceTol)124 void addConic(SkScalar weight, const SkPoint pts[], SkScalar srcSpaceTolSqd,
125 SkScalar srcSpaceTol) {
126 SkAutoConicToQuads converter;
127 const SkPoint* quadPts = converter.computeQuads(pts, weight, srcSpaceTol);
128 for (int i = 0; i < converter.countQuads(); ++i) {
129 this->addQuad(quadPts + i * 2, srcSpaceTolSqd, srcSpaceTol);
130 }
131 }
132
addCubic(const SkPoint pts[],SkScalar srcSpaceTolSqd,SkScalar srcSpaceTol)133 void addCubic(const SkPoint pts[], SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol) {
134 if (!this->ensureSpace(GrPathUtils::kMaxPointsPerCurve,
135 GrPathUtils::kMaxPointsPerCurve * this->indexScale(),
136 &pts[0])) {
137 return;
138 }
139
140 // First pt of cubic is the pt we ended on in previous step
141 uint16_t firstCPtIdx = this->currentIndex() - 1;
142 uint16_t numPts = (uint16_t) GrPathUtils::generateCubicPoints(
143 pts[0], pts[1], pts[2], pts[3], srcSpaceTolSqd, &fCurVert,
144 GrPathUtils::cubicPointCount(pts, srcSpaceTol));
145 if (this->isIndexed()) {
146 for (uint16_t i = 0; i < numPts; ++i) {
147 this->appendCountourEdgeIndices(firstCPtIdx + i);
148 }
149 }
150 }
151
addPath(const SkPath & path,SkScalar srcSpaceTol)152 void addPath(const SkPath& path, SkScalar srcSpaceTol) {
153 SkScalar srcSpaceTolSqd = srcSpaceTol * srcSpaceTol;
154
155 SkPath::Iter iter(path, false);
156 SkPoint pts[4];
157
158 bool done = false;
159 while (!done) {
160 SkPath::Verb verb = iter.next(pts);
161 switch (verb) {
162 case SkPath::kMove_Verb:
163 this->moveTo(pts[0]);
164 break;
165 case SkPath::kLine_Verb:
166 this->addLine(pts);
167 break;
168 case SkPath::kConic_Verb:
169 this->addConic(iter.conicWeight(), pts, srcSpaceTolSqd, srcSpaceTol);
170 break;
171 case SkPath::kQuad_Verb:
172 this->addQuad(pts, srcSpaceTolSqd, srcSpaceTol);
173 break;
174 case SkPath::kCubic_Verb:
175 this->addCubic(pts, srcSpaceTolSqd, srcSpaceTol);
176 break;
177 case SkPath::kClose_Verb:
178 break;
179 case SkPath::kDone_Verb:
180 done = true;
181 }
182 }
183 }
184
PathHasMultipleSubpaths(const SkPath & path)185 static bool PathHasMultipleSubpaths(const SkPath& path) {
186 bool first = true;
187
188 SkPath::Iter iter(path, false);
189 SkPath::Verb verb;
190
191 SkPoint pts[4];
192 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
193 if (SkPath::kMove_Verb == verb && !first) {
194 return true;
195 }
196 first = false;
197 }
198 return false;
199 }
200
201 private:
202 /**
203 * Derived properties
204 * TODO: Cache some of these for better performance, rather than re-computing?
205 */
isIndexed() const206 bool isIndexed() const {
207 return GrPrimitiveType::kLines == fPrimitiveType ||
208 GrPrimitiveType::kTriangles == fPrimitiveType;
209 }
isHairline() const210 bool isHairline() const {
211 return GrPrimitiveType::kLines == fPrimitiveType ||
212 GrPrimitiveType::kLineStrip == fPrimitiveType;
213 }
indexScale() const214 int indexScale() const {
215 switch (fPrimitiveType) {
216 case GrPrimitiveType::kLines:
217 return 2;
218 case GrPrimitiveType::kTriangles:
219 return 3;
220 default:
221 return 0;
222 }
223 }
224
currentIndex() const225 uint16_t currentIndex() const { return fCurVert - fVertices; }
226
227 // Allocate vertex and (possibly) index buffers
allocNewBuffers()228 void allocNewBuffers() {
229 SkASSERT(fValid);
230
231 // Ensure that we always get enough verts for a worst-case quad/cubic, plus leftover points
232 // from previous mesh piece (up to two verts to continue fanning). If we can't get that
233 // many, ask for a much larger number. This needs to be fairly big to handle quads/cubics,
234 // which have a worst-case of 1k points.
235 static const int kMinVerticesPerChunk = GrPathUtils::kMaxPointsPerCurve + 2;
236 static const int kFallbackVerticesPerChunk = 16384;
237
238 fVertices = static_cast<SkPoint*>(fTarget->makeVertexSpaceAtLeast(fVertexStride,
239 kMinVerticesPerChunk,
240 kFallbackVerticesPerChunk,
241 &fVertexBuffer,
242 &fFirstVertex,
243 &fVerticesInChunk));
244 if (!fVertices) {
245 SkDebugf("WARNING: Failed to allocate vertex buffer for DefaultPathRenderer.\n");
246 fCurVert = nullptr;
247 fCurIdx = fIndices = nullptr;
248 fSubpathIndexStart = 0;
249 fValid = false;
250 return;
251 }
252
253 if (this->isIndexed()) {
254 // Similar to above: Ensure we get enough indices for one worst-case quad/cubic.
255 // No extra indices are needed for stitching, though. If we can't get that many, ask
256 // for enough to match our large vertex request.
257 const int kMinIndicesPerChunk = GrPathUtils::kMaxPointsPerCurve * this->indexScale();
258 const int kFallbackIndicesPerChunk = kFallbackVerticesPerChunk * this->indexScale();
259
260 fIndices = fTarget->makeIndexSpaceAtLeast(kMinIndicesPerChunk, kFallbackIndicesPerChunk,
261 &fIndexBuffer, &fFirstIndex,
262 &fIndicesInChunk);
263 if (!fIndices) {
264 SkDebugf("WARNING: Failed to allocate index buffer for DefaultPathRenderer.\n");
265 fVertices = nullptr;
266 fValid = false;
267 }
268 }
269
270 fCurVert = fVertices;
271 fCurIdx = fIndices;
272 fSubpathIndexStart = 0;
273 }
274
appendCountourEdgeIndices(uint16_t edgeV0Idx)275 void appendCountourEdgeIndices(uint16_t edgeV0Idx) {
276 SkASSERT(fCurIdx);
277
278 // When drawing lines we're appending line segments along the countour. When applying the
279 // other fill rules we're drawing triangle fans around the start of the current (sub)path.
280 if (!this->isHairline()) {
281 *(fCurIdx++) = fSubpathIndexStart;
282 }
283 *(fCurIdx++) = edgeV0Idx;
284 *(fCurIdx++) = edgeV0Idx + 1;
285 }
286
287 // Emits a single draw with all accumulated vertex/index data
createMeshAndPutBackReserve()288 void createMeshAndPutBackReserve() {
289 if (!fValid) {
290 return;
291 }
292
293 int vertexCount = fCurVert - fVertices;
294 int indexCount = fCurIdx - fIndices;
295 SkASSERT(vertexCount <= fVerticesInChunk);
296 SkASSERT(indexCount <= fIndicesInChunk);
297
298 GrSimpleMesh* mesh = nullptr;
299 if (this->isIndexed() ? SkToBool(indexCount) : SkToBool(vertexCount)) {
300 mesh = fTarget->allocMesh();
301 if (!this->isIndexed()) {
302 mesh->set(std::move(fVertexBuffer), vertexCount, fFirstVertex);
303 } else {
304 mesh->setIndexed(std::move(fIndexBuffer), indexCount, fFirstIndex, 0,
305 vertexCount - 1, GrPrimitiveRestart::kNo, std::move(fVertexBuffer),
306 fFirstVertex);
307 }
308 }
309
310 fTarget->putBackIndices((size_t)(fIndicesInChunk - indexCount));
311 fTarget->putBackVertices((size_t)(fVerticesInChunk - vertexCount), fVertexStride);
312
313 if (mesh) {
314 fMeshes->push_back(mesh);
315 }
316 }
317
ensureSpace(int vertsNeeded,int indicesNeeded=0,const SkPoint * lastPoint=nullptr)318 bool ensureSpace(int vertsNeeded, int indicesNeeded = 0, const SkPoint* lastPoint = nullptr) {
319 if (!fValid) {
320 return false;
321 }
322
323 if (fCurVert + vertsNeeded > fVertices + fVerticesInChunk ||
324 fCurIdx + indicesNeeded > fIndices + fIndicesInChunk) {
325 // We are about to run out of space (possibly)
326
327 #ifdef SK_DEBUG
328 // To maintain continuity, we need to remember one or two points from the current mesh.
329 // Lines only need the last point, fills need the first point from the current contour.
330 // We always grab both here, and append the ones we need at the end of this process.
331 SkASSERT(fSubpathIndexStart < fVerticesInChunk);
332 // This assert is reading from the gpu buffer fVertices and will be slow, but for debug
333 // that is okay.
334 if (!this->isHairline()) {
335 SkASSERT(fSubpathStartPoint == fVertices[fSubpathIndexStart]);
336 }
337 if (lastPoint) {
338 SkASSERT(*(fCurVert - 1) == *lastPoint);
339 }
340 #endif
341
342 // Draw the mesh we've accumulated, and put back any unused space
343 this->createMeshAndPutBackReserve();
344
345 // Get new buffers
346 this->allocNewBuffers();
347 if (!fValid) {
348 return false;
349 }
350
351 // On moves we don't need to copy over any points to the new buffer and we pass in a
352 // null lastPoint.
353 if (lastPoint) {
354 // Append copies of the points we saved so the two meshes will weld properly
355 if (!this->isHairline()) {
356 *(fCurVert++) = fSubpathStartPoint;
357 }
358 *(fCurVert++) = *lastPoint;
359 }
360 }
361
362 return true;
363 }
364
365 GrPrimitiveType fPrimitiveType;
366 GrMeshDrawTarget* fTarget;
367 size_t fVertexStride;
368
369 sk_sp<const GrBuffer> fVertexBuffer;
370 int fFirstVertex;
371 int fVerticesInChunk;
372 SkPoint* fVertices;
373 SkPoint* fCurVert;
374
375 sk_sp<const GrBuffer> fIndexBuffer;
376 int fFirstIndex;
377 int fIndicesInChunk;
378 uint16_t* fIndices;
379 uint16_t* fCurIdx;
380 uint16_t fSubpathIndexStart;
381 SkPoint fSubpathStartPoint;
382
383 bool fValid = true;
384 SkTDArray<GrSimpleMesh*>* fMeshes;
385 };
386
387 class DefaultPathOp final : public GrMeshDrawOp {
388 private:
389 using Helper = GrSimpleMeshDrawOpHelperWithStencil;
390
391 public:
392 DEFINE_OP_CLASS_ID
393
Make(GrRecordingContext * context,GrPaint && paint,const SkPath & path,SkScalar tolerance,uint8_t coverage,const SkMatrix & viewMatrix,bool isHairline,GrAAType aaType,const SkRect & devBounds,const GrUserStencilSettings * stencilSettings)394 static GrOp::Owner Make(GrRecordingContext* context,
395 GrPaint&& paint,
396 const SkPath& path,
397 SkScalar tolerance,
398 uint8_t coverage,
399 const SkMatrix& viewMatrix,
400 bool isHairline,
401 GrAAType aaType,
402 const SkRect& devBounds,
403 const GrUserStencilSettings* stencilSettings) {
404 return Helper::FactoryHelper<DefaultPathOp>(context, std::move(paint), path, tolerance,
405 coverage, viewMatrix, isHairline, aaType,
406 devBounds, stencilSettings);
407 }
408
name() const409 const char* name() const override { return "DefaultPathOp"; }
410
visitProxies(const GrVisitProxyFunc & func) const411 void visitProxies(const GrVisitProxyFunc& func) const override {
412 if (fProgramInfo) {
413 fProgramInfo->visitFPProxies(func);
414 } else {
415 fHelper.visitProxies(func);
416 }
417 }
418
DefaultPathOp(GrProcessorSet * processorSet,const SkPMColor4f & color,const SkPath & path,SkScalar tolerance,uint8_t coverage,const SkMatrix & viewMatrix,bool isHairline,GrAAType aaType,const SkRect & devBounds,const GrUserStencilSettings * stencilSettings)419 DefaultPathOp(GrProcessorSet* processorSet, const SkPMColor4f& color, const SkPath& path,
420 SkScalar tolerance, uint8_t coverage, const SkMatrix& viewMatrix, bool isHairline,
421 GrAAType aaType, const SkRect& devBounds,
422 const GrUserStencilSettings* stencilSettings)
423 : INHERITED(ClassID())
424 , fHelper(processorSet, aaType, stencilSettings)
425 , fColor(color)
426 , fCoverage(coverage)
427 , fViewMatrix(viewMatrix)
428 , fIsHairline(isHairline) {
429 fPaths.emplace_back(PathData{path, tolerance});
430
431 HasAABloat aaBloat = (aaType == GrAAType::kNone) ? HasAABloat ::kNo : HasAABloat::kYes;
432 this->setBounds(devBounds, aaBloat,
433 isHairline ? IsHairline::kYes : IsHairline::kNo);
434 }
435
fixedFunctionFlags() const436 FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
437
finalize(const GrCaps & caps,const GrAppliedClip * clip,GrClampType clampType)438 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
439 GrClampType clampType) override {
440 GrProcessorAnalysisCoverage gpCoverage =
441 this->coverage() == 0xFF ? GrProcessorAnalysisCoverage::kNone
442 : GrProcessorAnalysisCoverage::kSingleChannel;
443 // This Op uses uniform (not vertex) color, so doesn't need to track wide color.
444 return fHelper.finalizeProcessors(caps, clip, clampType, gpCoverage, &fColor, nullptr);
445 }
446
447 private:
primType() const448 GrPrimitiveType primType() const {
449 if (this->isHairline()) {
450 int instanceCount = fPaths.count();
451
452 // We avoid indices when we have a single hairline contour.
453 bool isIndexed = instanceCount > 1 ||
454 PathGeoBuilder::PathHasMultipleSubpaths(fPaths[0].fPath);
455
456 return isIndexed ? GrPrimitiveType::kLines : GrPrimitiveType::kLineStrip;
457 }
458
459 return GrPrimitiveType::kTriangles;
460 }
461
programInfo()462 GrProgramInfo* programInfo() override { return fProgramInfo; }
463
onCreateProgramInfo(const GrCaps * caps,SkArenaAlloc * arena,const GrSurfaceProxyView & writeView,bool usesMSAASurface,GrAppliedClip && appliedClip,const GrDstProxyView & dstProxyView,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)464 void onCreateProgramInfo(const GrCaps* caps,
465 SkArenaAlloc* arena,
466 const GrSurfaceProxyView& writeView,
467 bool usesMSAASurface,
468 GrAppliedClip&& appliedClip,
469 const GrDstProxyView& dstProxyView,
470 GrXferBarrierFlags renderPassXferBarriers,
471 GrLoadOp colorLoadOp) override {
472 GrGeometryProcessor* gp;
473 {
474 using namespace GrDefaultGeoProcFactory;
475 Color color(this->color());
476 Coverage coverage(this->coverage());
477 LocalCoords localCoords(fHelper.usesLocalCoords() ? LocalCoords::kUsePosition_Type
478 : LocalCoords::kUnused_Type);
479 gp = GrDefaultGeoProcFactory::Make(arena,
480 color,
481 coverage,
482 localCoords,
483 this->viewMatrix());
484 }
485
486 SkASSERT(gp->vertexStride() == sizeof(SkPoint));
487
488 fProgramInfo = fHelper.createProgramInfoWithStencil(caps, arena, writeView,
489 usesMSAASurface,
490 std::move(appliedClip), dstProxyView,
491 gp, this->primType(),
492 renderPassXferBarriers, colorLoadOp);
493
494 }
495
onPrepareDraws(GrMeshDrawTarget * target)496 void onPrepareDraws(GrMeshDrawTarget* target) override {
497 PathGeoBuilder pathGeoBuilder(this->primType(), target, &fMeshes);
498
499 // fill buffers
500 for (int i = 0; i < fPaths.count(); i++) {
501 const PathData& args = fPaths[i];
502 pathGeoBuilder.addPath(args.fPath, args.fTolerance);
503 }
504 }
505
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)506 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
507 if (!fProgramInfo) {
508 this->createProgramInfo(flushState);
509 }
510
511 if (!fProgramInfo || !fMeshes.count()) {
512 return;
513 }
514
515 flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
516 flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
517 for (int i = 0; i < fMeshes.count(); ++i) {
518 flushState->drawMesh(*fMeshes[i]);
519 }
520 }
521
onCombineIfPossible(GrOp * t,SkArenaAlloc *,const GrCaps & caps)522 CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override {
523 DefaultPathOp* that = t->cast<DefaultPathOp>();
524 if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
525 return CombineResult::kCannotCombine;
526 }
527
528 if (this->color() != that->color()) {
529 return CombineResult::kCannotCombine;
530 }
531
532 if (this->coverage() != that->coverage()) {
533 return CombineResult::kCannotCombine;
534 }
535
536 if (!SkMatrixPriv::CheapEqual(this->viewMatrix(), that->viewMatrix())) {
537 return CombineResult::kCannotCombine;
538 }
539
540 if (this->isHairline() != that->isHairline()) {
541 return CombineResult::kCannotCombine;
542 }
543
544 fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin());
545 return CombineResult::kMerged;
546 }
547
548 #if GR_TEST_UTILS
onDumpInfo() const549 SkString onDumpInfo() const override {
550 SkString string = SkStringPrintf("Color: 0x%08x Count: %d\n",
551 fColor.toBytes_RGBA(), fPaths.count());
552 for (const auto& path : fPaths) {
553 string.appendf("Tolerance: %.2f\n", path.fTolerance);
554 }
555 string += fHelper.dumpInfo();
556 return string;
557 }
558 #endif
559
color() const560 const SkPMColor4f& color() const { return fColor; }
coverage() const561 uint8_t coverage() const { return fCoverage; }
viewMatrix() const562 const SkMatrix& viewMatrix() const { return fViewMatrix; }
isHairline() const563 bool isHairline() const { return fIsHairline; }
564
565 struct PathData {
566 SkPath fPath;
567 SkScalar fTolerance;
568 };
569
570 SkSTArray<1, PathData, true> fPaths;
571 Helper fHelper;
572 SkPMColor4f fColor;
573 uint8_t fCoverage;
574 SkMatrix fViewMatrix;
575 bool fIsHairline;
576
577 SkTDArray<GrSimpleMesh*> fMeshes;
578 GrProgramInfo* fProgramInfo = nullptr;
579
580 using INHERITED = GrMeshDrawOp;
581 };
582
583 } // anonymous namespace
584
585 ///////////////////////////////////////////////////////////////////////////////////////////////////
586
587 #if GR_TEST_UTILS
588
GR_DRAW_OP_TEST_DEFINE(DefaultPathOp)589 GR_DRAW_OP_TEST_DEFINE(DefaultPathOp) {
590 SkMatrix viewMatrix = GrTest::TestMatrix(random);
591
592 // For now just hairlines because the other types of draws require two ops.
593 // TODO we should figure out a way to combine the stencil and cover steps into one op.
594 GrStyle style(SkStrokeRec::kHairline_InitStyle);
595 const SkPath& path = GrTest::TestPath(random);
596
597 // Compute srcSpaceTol
598 SkRect bounds = path.getBounds();
599 SkScalar tol = GrPathUtils::kDefaultTolerance;
600 SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, bounds);
601
602 viewMatrix.mapRect(&bounds);
603 uint8_t coverage = GrTest::RandomCoverage(random);
604 GrAAType aaType = GrAAType::kNone;
605 if (numSamples > 1 && random->nextBool()) {
606 aaType = GrAAType::kMSAA;
607 }
608 return DefaultPathOp::Make(context, std::move(paint), path, srcSpaceTol, coverage, viewMatrix,
609 true, aaType, bounds, GrGetRandomStencil(random, context));
610 }
611
612 #endif
613
614 ///////////////////////////////////////////////////////////////////////////////////////////////////
615
616 namespace skgpu::v1 {
617
internalDrawPath(skgpu::v1::SurfaceDrawContext * sdc,GrPaint && paint,GrAAType aaType,const GrUserStencilSettings & userStencilSettings,const GrClip * clip,const SkMatrix & viewMatrix,const GrStyledShape & shape,bool stencilOnly)618 bool DefaultPathRenderer::internalDrawPath(skgpu::v1::SurfaceDrawContext* sdc,
619 GrPaint&& paint,
620 GrAAType aaType,
621 const GrUserStencilSettings& userStencilSettings,
622 const GrClip* clip,
623 const SkMatrix& viewMatrix,
624 const GrStyledShape& shape,
625 bool stencilOnly) {
626 auto context = sdc->recordingContext();
627
628 SkASSERT(GrAAType::kCoverage != aaType);
629 SkPath path;
630 shape.asPath(&path);
631
632 SkScalar hairlineCoverage;
633 uint8_t newCoverage = 0xff;
634 bool isHairline = false;
635 if (GrIsStrokeHairlineOrEquivalent(shape.style(), viewMatrix, &hairlineCoverage)) {
636 newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff);
637 isHairline = true;
638 } else {
639 SkASSERT(shape.style().isSimpleFill());
640 }
641
642 int passCount = 0;
643 const GrUserStencilSettings* passes[2];
644 bool reverse = false;
645 bool lastPassIsBounds;
646
647 if (isHairline) {
648 passCount = 1;
649 if (stencilOnly) {
650 passes[0] = &gDirectToStencil;
651 } else {
652 passes[0] = &userStencilSettings;
653 }
654 lastPassIsBounds = false;
655 } else {
656 if (single_pass_shape(shape)) {
657 passCount = 1;
658 if (stencilOnly) {
659 passes[0] = &gDirectToStencil;
660 } else {
661 passes[0] = &userStencilSettings;
662 }
663 lastPassIsBounds = false;
664 } else {
665 switch (path.getFillType()) {
666 case SkPathFillType::kInverseEvenOdd:
667 reverse = true;
668 [[fallthrough]];
669 case SkPathFillType::kEvenOdd:
670 passes[0] = &gEOStencilPass;
671 if (stencilOnly) {
672 passCount = 1;
673 lastPassIsBounds = false;
674 } else {
675 passCount = 2;
676 lastPassIsBounds = true;
677 if (reverse) {
678 passes[1] = &gInvEOColorPass;
679 } else {
680 passes[1] = &gEOColorPass;
681 }
682 }
683 break;
684
685 case SkPathFillType::kInverseWinding:
686 reverse = true;
687 [[fallthrough]];
688 case SkPathFillType::kWinding:
689 passes[0] = &gWindStencilPass;
690 passCount = 2;
691 if (stencilOnly) {
692 lastPassIsBounds = false;
693 --passCount;
694 } else {
695 lastPassIsBounds = true;
696 if (reverse) {
697 passes[passCount-1] = &gInvWindColorPass;
698 } else {
699 passes[passCount-1] = &gWindColorPass;
700 }
701 }
702 break;
703 default:
704 SkDEBUGFAIL("Unknown path fFill!");
705 return false;
706 }
707 }
708 }
709
710 SkScalar tol = GrPathUtils::kDefaultTolerance;
711 SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, path.getBounds());
712
713 SkRect devBounds;
714 GetPathDevBounds(path, sdc->asRenderTargetProxy()->backingStoreDimensions(),
715 viewMatrix, &devBounds);
716
717 for (int p = 0; p < passCount; ++p) {
718 if (lastPassIsBounds && (p == passCount-1)) {
719 SkRect bounds;
720 SkMatrix localMatrix = SkMatrix::I();
721 if (reverse) {
722 // draw over the dev bounds (which will be the whole dst surface for inv fill).
723 bounds = devBounds;
724 SkMatrix vmi;
725 // mapRect through persp matrix may not be correct
726 if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) {
727 vmi.mapRect(&bounds);
728 } else {
729 if (!viewMatrix.invert(&localMatrix)) {
730 return false;
731 }
732 }
733 } else {
734 bounds = path.getBounds();
735 }
736 const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() :
737 viewMatrix;
738 // This is a non-coverage aa rect op since we assert aaType != kCoverage at the start
739 assert_alive(paint);
740 sdc->stencilRect(clip, passes[p], std::move(paint),
741 GrAA(aaType == GrAAType::kMSAA), viewM, bounds,
742 &localMatrix);
743 } else {
744 bool stencilPass = stencilOnly || passCount > 1;
745 GrOp::Owner op;
746 if (stencilPass) {
747 GrPaint stencilPaint;
748 stencilPaint.setXPFactory(GrDisableColorXPFactory::Get());
749 op = DefaultPathOp::Make(context, std::move(stencilPaint), path, srcSpaceTol,
750 newCoverage, viewMatrix, isHairline, aaType, devBounds,
751 passes[p]);
752 } else {
753 assert_alive(paint);
754 op = DefaultPathOp::Make(context, std::move(paint), path, srcSpaceTol, newCoverage,
755 viewMatrix, isHairline, aaType, devBounds, passes[p]);
756 }
757 sdc->addDrawOp(clip, std::move(op));
758 }
759 }
760 return true;
761 }
762
763
764 PathRenderer::StencilSupport
onGetStencilSupport(const GrStyledShape & shape) const765 DefaultPathRenderer::onGetStencilSupport(const GrStyledShape& shape) const {
766 if (single_pass_shape(shape)) {
767 return kNoRestriction_StencilSupport;
768 } else {
769 return kStencilOnly_StencilSupport;
770 }
771 }
772
onCanDrawPath(const CanDrawPathArgs & args) const773 PathRenderer::CanDrawPath DefaultPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
774 bool isHairline = GrIsStrokeHairlineOrEquivalent(
775 args.fShape->style(), *args.fViewMatrix, nullptr);
776 // If we aren't a single_pass_shape or hairline, we require stencil buffers.
777 if (!(single_pass_shape(*args.fShape) || isHairline) &&
778 !args.fProxy->canUseStencil(*args.fCaps)) {
779 return CanDrawPath::kNo;
780 }
781 // If antialiasing is required, we only support MSAA.
782 if (GrAAType::kNone != args.fAAType && GrAAType::kMSAA != args.fAAType) {
783 return CanDrawPath::kNo;
784 }
785 // This can draw any path with any simple fill style.
786 if (!args.fShape->style().isSimpleFill() && !isHairline) {
787 return CanDrawPath::kNo;
788 }
789 // This is the fallback renderer for when a path is too complicated for the others to draw.
790 return CanDrawPath::kAsBackup;
791 }
792
onDrawPath(const DrawPathArgs & args)793 bool DefaultPathRenderer::onDrawPath(const DrawPathArgs& args) {
794 GR_AUDIT_TRAIL_AUTO_FRAME(args.fContext->priv().auditTrail(),
795 "DefaultPathRenderer::onDrawPath");
796 GrAAType aaType = (GrAAType::kNone != args.fAAType) ? GrAAType::kMSAA : GrAAType::kNone;
797
798 return this->internalDrawPath(
799 args.fSurfaceDrawContext, std::move(args.fPaint), aaType, *args.fUserStencilSettings,
800 args.fClip, *args.fViewMatrix, *args.fShape, false);
801 }
802
onStencilPath(const StencilPathArgs & args)803 void DefaultPathRenderer::onStencilPath(const StencilPathArgs& args) {
804 GR_AUDIT_TRAIL_AUTO_FRAME(args.fContext->priv().auditTrail(),
805 "DefaultPathRenderer::onStencilPath");
806 SkASSERT(!args.fShape->inverseFilled());
807
808 GrPaint paint;
809 paint.setXPFactory(GrDisableColorXPFactory::Get());
810
811 auto aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone;
812
813 this->internalDrawPath(
814 args.fSurfaceDrawContext, std::move(paint), aaType, GrUserStencilSettings::kUnused,
815 args.fClip, *args.fViewMatrix, *args.fShape, true);
816 }
817
818 } // namespace skgpu::v1
819