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