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