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 "GrDefaultPathRenderer.h"
9
10 #include "GrContext.h"
11 #include "GrDefaultGeoProcFactory.h"
12 #include "GrDrawOpTest.h"
13 #include "GrFixedClip.h"
14 #include "GrMesh.h"
15 #include "GrOpFlushState.h"
16 #include "GrPathUtils.h"
17 #include "GrPipelineBuilder.h"
18 #include "SkGeometry.h"
19 #include "SkString.h"
20 #include "SkStrokeRec.h"
21 #include "SkTLazy.h"
22 #include "SkTraceEvent.h"
23
24 #include "ops/GrMeshDrawOp.h"
25 #include "ops/GrRectOpFactory.h"
26
GrDefaultPathRenderer(bool separateStencilSupport,bool stencilWrapOpsSupport)27 GrDefaultPathRenderer::GrDefaultPathRenderer(bool separateStencilSupport,
28 bool stencilWrapOpsSupport)
29 : fSeparateStencil(separateStencilSupport)
30 , fStencilWrapOps(stencilWrapOpsSupport) {
31 }
32
33 ////////////////////////////////////////////////////////////////////////////////
34 // Helpers for drawPath
35
36 #define STENCIL_OFF 0 // Always disable stencil (even when needed)
37
single_pass_shape(const GrShape & shape)38 static inline bool single_pass_shape(const GrShape& shape) {
39 #if STENCIL_OFF
40 return true;
41 #else
42 // Inverse fill is always two pass.
43 if (shape.inverseFilled()) {
44 return false;
45 }
46 // This path renderer only accepts simple fill paths or stroke paths that are either hairline
47 // or have a stroke width small enough to treat as hairline. Hairline paths are always single
48 // pass. Filled paths are single pass if they're convex.
49 if (shape.style().isSimpleFill()) {
50 return shape.knownToBeConvex();
51 }
52 return true;
53 #endif
54 }
55
56 GrPathRenderer::StencilSupport
onGetStencilSupport(const GrShape & shape) const57 GrDefaultPathRenderer::onGetStencilSupport(const GrShape& shape) const {
58 if (single_pass_shape(shape)) {
59 return GrPathRenderer::kNoRestriction_StencilSupport;
60 } else {
61 return GrPathRenderer::kStencilOnly_StencilSupport;
62 }
63 }
64
append_countour_edge_indices(bool hairLine,uint16_t fanCenterIdx,uint16_t edgeV0Idx,uint16_t ** indices)65 static inline void append_countour_edge_indices(bool hairLine,
66 uint16_t fanCenterIdx,
67 uint16_t edgeV0Idx,
68 uint16_t** indices) {
69 // when drawing lines we're appending line segments along
70 // the contour. When applying the other fill rules we're
71 // drawing triangle fans around fanCenterIdx.
72 if (!hairLine) {
73 *((*indices)++) = fanCenterIdx;
74 }
75 *((*indices)++) = edgeV0Idx;
76 *((*indices)++) = edgeV0Idx + 1;
77 }
78
add_quad(SkPoint ** vert,const SkPoint * base,const SkPoint pts[],SkScalar srcSpaceTolSqd,SkScalar srcSpaceTol,bool indexed,bool isHairline,uint16_t subpathIdxStart,int offset,uint16_t ** idx)79 static inline void add_quad(SkPoint** vert, const SkPoint* base, const SkPoint pts[],
80 SkScalar srcSpaceTolSqd, SkScalar srcSpaceTol, bool indexed,
81 bool isHairline, uint16_t subpathIdxStart, int offset, uint16_t** idx) {
82 // first pt of quad is the pt we ended on in previous step
83 uint16_t firstQPtIdx = (uint16_t)(*vert - base) - 1 + offset;
84 uint16_t numPts = (uint16_t)
85 GrPathUtils::generateQuadraticPoints(
86 pts[0], pts[1], pts[2],
87 srcSpaceTolSqd, vert,
88 GrPathUtils::quadraticPointCount(pts, srcSpaceTol));
89 if (indexed) {
90 for (uint16_t i = 0; i < numPts; ++i) {
91 append_countour_edge_indices(isHairline, subpathIdxStart,
92 firstQPtIdx + i, idx);
93 }
94 }
95 }
96
97 class DefaultPathOp final : public GrMeshDrawOp {
98 public:
99 DEFINE_OP_CLASS_ID
100
Make(GrColor color,const SkPath & path,SkScalar tolerance,uint8_t coverage,const SkMatrix & viewMatrix,bool isHairline,const SkRect & devBounds)101 static std::unique_ptr<GrMeshDrawOp> Make(GrColor color, const SkPath& path, SkScalar tolerance,
102 uint8_t coverage, const SkMatrix& viewMatrix,
103 bool isHairline, const SkRect& devBounds) {
104 return std::unique_ptr<GrMeshDrawOp>(new DefaultPathOp(color, path, tolerance, coverage,
105 viewMatrix, isHairline, devBounds));
106 }
107
name() const108 const char* name() const override { return "DefaultPathOp"; }
109
dumpInfo() const110 SkString dumpInfo() const override {
111 SkString string;
112 string.appendf("Color: 0x%08x Count: %d\n", fColor, fPaths.count());
113 for (const auto& path : fPaths) {
114 string.appendf("Tolerance: %.2f\n", path.fTolerance);
115 }
116 string.append(DumpPipelineInfo(*this->pipeline()));
117 string.append(INHERITED::dumpInfo());
118 return string;
119 }
120
121 private:
DefaultPathOp(GrColor color,const SkPath & path,SkScalar tolerance,uint8_t coverage,const SkMatrix & viewMatrix,bool isHairline,const SkRect & devBounds)122 DefaultPathOp(GrColor color, const SkPath& path, SkScalar tolerance, uint8_t coverage,
123 const SkMatrix& viewMatrix, bool isHairline, const SkRect& devBounds)
124 : INHERITED(ClassID())
125 , fColor(color)
126 , fCoverage(coverage)
127 , fViewMatrix(viewMatrix)
128 , fIsHairline(isHairline) {
129 fPaths.emplace_back(PathData{path, tolerance});
130
131 this->setBounds(devBounds, HasAABloat::kNo,
132 isHairline ? IsZeroArea::kYes : IsZeroArea::kNo);
133 }
134
getFragmentProcessorAnalysisInputs(GrPipelineAnalysisColor * color,GrPipelineAnalysisCoverage * coverage) const135 void getFragmentProcessorAnalysisInputs(GrPipelineAnalysisColor* color,
136 GrPipelineAnalysisCoverage* coverage) const override {
137 color->setToConstant(fColor);
138 *coverage = this->coverage() == 0xff ? GrPipelineAnalysisCoverage::kNone
139 : GrPipelineAnalysisCoverage::kSingleChannel;
140 }
141
applyPipelineOptimizations(const GrPipelineOptimizations & optimizations)142 void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
143 optimizations.getOverrideColorIfSet(&fColor);
144 fUsesLocalCoords = optimizations.readsLocalCoords();
145 }
146
onPrepareDraws(Target * target) const147 void onPrepareDraws(Target* target) const override {
148 sk_sp<GrGeometryProcessor> gp;
149 {
150 using namespace GrDefaultGeoProcFactory;
151 Color color(this->color());
152 Coverage coverage(this->coverage());
153 LocalCoords localCoords(this->usesLocalCoords() ? LocalCoords::kUsePosition_Type :
154 LocalCoords::kUnused_Type);
155 gp = GrDefaultGeoProcFactory::Make(color, coverage, localCoords, this->viewMatrix());
156 }
157
158 size_t vertexStride = gp->getVertexStride();
159 SkASSERT(vertexStride == sizeof(SkPoint));
160
161 int instanceCount = fPaths.count();
162
163 // compute number of vertices
164 int maxVertices = 0;
165
166 // We will use index buffers if we have multiple paths or one path with multiple contours
167 bool isIndexed = instanceCount > 1;
168 for (int i = 0; i < instanceCount; i++) {
169 const PathData& args = fPaths[i];
170
171 int contourCount;
172 maxVertices += GrPathUtils::worstCasePointCount(args.fPath, &contourCount,
173 args.fTolerance);
174
175 isIndexed = isIndexed || contourCount > 1;
176 }
177
178 if (maxVertices == 0 || maxVertices > ((int)SK_MaxU16 + 1)) {
179 //SkDebugf("Cannot render path (%d)\n", maxVertices);
180 return;
181 }
182
183 // determine primitiveType
184 int maxIndices = 0;
185 GrPrimitiveType primitiveType;
186 if (this->isHairline()) {
187 if (isIndexed) {
188 maxIndices = 2 * maxVertices;
189 primitiveType = kLines_GrPrimitiveType;
190 } else {
191 primitiveType = kLineStrip_GrPrimitiveType;
192 }
193 } else {
194 if (isIndexed) {
195 maxIndices = 3 * maxVertices;
196 primitiveType = kTriangles_GrPrimitiveType;
197 } else {
198 primitiveType = kTriangleFan_GrPrimitiveType;
199 }
200 }
201
202 // allocate vertex / index buffers
203 const GrBuffer* vertexBuffer;
204 int firstVertex;
205
206 void* verts = target->makeVertexSpace(vertexStride, maxVertices,
207 &vertexBuffer, &firstVertex);
208
209 if (!verts) {
210 SkDebugf("Could not allocate vertices\n");
211 return;
212 }
213
214 const GrBuffer* indexBuffer = nullptr;
215 int firstIndex = 0;
216
217 void* indices = nullptr;
218 if (isIndexed) {
219 indices = target->makeIndexSpace(maxIndices, &indexBuffer, &firstIndex);
220
221 if (!indices) {
222 SkDebugf("Could not allocate indices\n");
223 return;
224 }
225 }
226
227 // fill buffers
228 int vertexOffset = 0;
229 int indexOffset = 0;
230 for (int i = 0; i < instanceCount; i++) {
231 const PathData& args = fPaths[i];
232
233 int vertexCnt = 0;
234 int indexCnt = 0;
235 if (!this->createGeom(verts,
236 vertexOffset,
237 indices,
238 indexOffset,
239 &vertexCnt,
240 &indexCnt,
241 args.fPath,
242 args.fTolerance,
243 isIndexed)) {
244 return;
245 }
246
247 vertexOffset += vertexCnt;
248 indexOffset += indexCnt;
249 SkASSERT(vertexOffset <= maxVertices && indexOffset <= maxIndices);
250 }
251
252 GrMesh mesh;
253 if (isIndexed) {
254 mesh.initIndexed(primitiveType, vertexBuffer, indexBuffer, firstVertex, firstIndex,
255 vertexOffset, indexOffset);
256 } else {
257 mesh.init(primitiveType, vertexBuffer, firstVertex, vertexOffset);
258 }
259 target->draw(gp.get(), mesh);
260
261 // put back reserves
262 target->putBackIndices((size_t)(maxIndices - indexOffset));
263 target->putBackVertices((size_t)(maxVertices - vertexOffset), (size_t)vertexStride);
264 }
265
onCombineIfPossible(GrOp * t,const GrCaps & caps)266 bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
267 DefaultPathOp* that = t->cast<DefaultPathOp>();
268 if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
269 that->bounds(), caps)) {
270 return false;
271 }
272
273 if (this->color() != that->color()) {
274 return false;
275 }
276
277 if (this->coverage() != that->coverage()) {
278 return false;
279 }
280
281 if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
282 return false;
283 }
284
285 if (this->isHairline() != that->isHairline()) {
286 return false;
287 }
288
289 fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin());
290 this->joinBounds(*that);
291 return true;
292 }
293
createGeom(void * vertices,size_t vertexOffset,void * indices,size_t indexOffset,int * vertexCnt,int * indexCnt,const SkPath & path,SkScalar srcSpaceTol,bool isIndexed) const294 bool createGeom(void* vertices,
295 size_t vertexOffset,
296 void* indices,
297 size_t indexOffset,
298 int* vertexCnt,
299 int* indexCnt,
300 const SkPath& path,
301 SkScalar srcSpaceTol,
302 bool isIndexed) const {
303 SkScalar srcSpaceTolSqd = srcSpaceTol * srcSpaceTol;
304
305 uint16_t indexOffsetU16 = (uint16_t)indexOffset;
306 uint16_t vertexOffsetU16 = (uint16_t)vertexOffset;
307
308 uint16_t* idxBase = reinterpret_cast<uint16_t*>(indices) + indexOffsetU16;
309 uint16_t* idx = idxBase;
310 uint16_t subpathIdxStart = vertexOffsetU16;
311
312 SkPoint* base = reinterpret_cast<SkPoint*>(vertices) + vertexOffset;
313 SkPoint* vert = base;
314
315 SkPoint pts[4];
316
317 bool first = true;
318 int subpath = 0;
319
320 SkPath::Iter iter(path, false);
321
322 bool done = false;
323 while (!done) {
324 SkPath::Verb verb = iter.next(pts);
325 switch (verb) {
326 case SkPath::kMove_Verb:
327 if (!first) {
328 uint16_t currIdx = (uint16_t) (vert - base) + vertexOffsetU16;
329 subpathIdxStart = currIdx;
330 ++subpath;
331 }
332 *vert = pts[0];
333 vert++;
334 break;
335 case SkPath::kLine_Verb:
336 if (isIndexed) {
337 uint16_t prevIdx = (uint16_t)(vert - base) - 1 + vertexOffsetU16;
338 append_countour_edge_indices(this->isHairline(), subpathIdxStart,
339 prevIdx, &idx);
340 }
341 *(vert++) = pts[1];
342 break;
343 case SkPath::kConic_Verb: {
344 SkScalar weight = iter.conicWeight();
345 SkAutoConicToQuads converter;
346 // Converting in src-space, hance the finer tolerance (0.25)
347 // TODO: find a way to do this in dev-space so the tolerance means something
348 const SkPoint* quadPts = converter.computeQuads(pts, weight, 0.25f);
349 for (int i = 0; i < converter.countQuads(); ++i) {
350 add_quad(&vert, base, quadPts + i*2, srcSpaceTolSqd, srcSpaceTol,
351 isIndexed, this->isHairline(), subpathIdxStart,
352 (int)vertexOffset, &idx);
353 }
354 break;
355 }
356 case SkPath::kQuad_Verb:
357 add_quad(&vert, base, pts, srcSpaceTolSqd, srcSpaceTol, isIndexed,
358 this->isHairline(), subpathIdxStart, (int)vertexOffset, &idx);
359 break;
360 case SkPath::kCubic_Verb: {
361 // first pt of cubic is the pt we ended on in previous step
362 uint16_t firstCPtIdx = (uint16_t)(vert - base) - 1 + vertexOffsetU16;
363 uint16_t numPts = (uint16_t) GrPathUtils::generateCubicPoints(
364 pts[0], pts[1], pts[2], pts[3],
365 srcSpaceTolSqd, &vert,
366 GrPathUtils::cubicPointCount(pts, srcSpaceTol));
367 if (isIndexed) {
368 for (uint16_t i = 0; i < numPts; ++i) {
369 append_countour_edge_indices(this->isHairline(), subpathIdxStart,
370 firstCPtIdx + i, &idx);
371 }
372 }
373 break;
374 }
375 case SkPath::kClose_Verb:
376 break;
377 case SkPath::kDone_Verb:
378 done = true;
379 }
380 first = false;
381 }
382
383 *vertexCnt = static_cast<int>(vert - base);
384 *indexCnt = static_cast<int>(idx - idxBase);
385 return true;
386 }
387
color() const388 GrColor color() const { return fColor; }
coverage() const389 uint8_t coverage() const { return fCoverage; }
usesLocalCoords() const390 bool usesLocalCoords() const { return fUsesLocalCoords; }
viewMatrix() const391 const SkMatrix& viewMatrix() const { return fViewMatrix; }
isHairline() const392 bool isHairline() const { return fIsHairline; }
393
394 struct PathData {
395 SkPath fPath;
396 SkScalar fTolerance;
397 };
398
399 GrColor fColor;
400 uint8_t fCoverage;
401 SkMatrix fViewMatrix;
402 bool fUsesLocalCoords;
403 bool fIsHairline;
404 SkSTArray<1, PathData, true> fPaths;
405
406 typedef GrMeshDrawOp INHERITED;
407 };
408
internalDrawPath(GrRenderTargetContext * renderTargetContext,GrPaint && paint,GrAAType aaType,const GrUserStencilSettings & userStencilSettings,const GrClip & clip,const SkMatrix & viewMatrix,const GrShape & shape,bool stencilOnly)409 bool GrDefaultPathRenderer::internalDrawPath(GrRenderTargetContext* renderTargetContext,
410 GrPaint&& paint,
411 GrAAType aaType,
412 const GrUserStencilSettings& userStencilSettings,
413 const GrClip& clip,
414 const SkMatrix& viewMatrix,
415 const GrShape& shape,
416 bool stencilOnly) {
417 SkASSERT(GrAAType::kCoverage != aaType);
418 SkPath path;
419 shape.asPath(&path);
420
421 SkScalar hairlineCoverage;
422 uint8_t newCoverage = 0xff;
423 bool isHairline = false;
424 if (IsStrokeHairlineOrEquivalent(shape.style(), viewMatrix, &hairlineCoverage)) {
425 newCoverage = SkScalarRoundToInt(hairlineCoverage * 0xff);
426 isHairline = true;
427 } else {
428 SkASSERT(shape.style().isSimpleFill());
429 }
430
431 int passCount = 0;
432 const GrUserStencilSettings* passes[3];
433 GrDrawFace drawFace[3];
434 bool reverse = false;
435 bool lastPassIsBounds;
436
437 if (isHairline) {
438 passCount = 1;
439 if (stencilOnly) {
440 passes[0] = &gDirectToStencil;
441 } else {
442 passes[0] = &userStencilSettings;
443 }
444 lastPassIsBounds = false;
445 drawFace[0] = GrDrawFace::kBoth;
446 } else {
447 if (single_pass_shape(shape)) {
448 passCount = 1;
449 if (stencilOnly) {
450 passes[0] = &gDirectToStencil;
451 } else {
452 passes[0] = &userStencilSettings;
453 }
454 drawFace[0] = GrDrawFace::kBoth;
455 lastPassIsBounds = false;
456 } else {
457 switch (path.getFillType()) {
458 case SkPath::kInverseEvenOdd_FillType:
459 reverse = true;
460 // fallthrough
461 case SkPath::kEvenOdd_FillType:
462 passes[0] = &gEOStencilPass;
463 if (stencilOnly) {
464 passCount = 1;
465 lastPassIsBounds = false;
466 } else {
467 passCount = 2;
468 lastPassIsBounds = true;
469 if (reverse) {
470 passes[1] = &gInvEOColorPass;
471 } else {
472 passes[1] = &gEOColorPass;
473 }
474 }
475 drawFace[0] = drawFace[1] = GrDrawFace::kBoth;
476 break;
477
478 case SkPath::kInverseWinding_FillType:
479 reverse = true;
480 // fallthrough
481 case SkPath::kWinding_FillType:
482 if (fSeparateStencil) {
483 if (fStencilWrapOps) {
484 passes[0] = &gWindStencilSeparateWithWrap;
485 } else {
486 passes[0] = &gWindStencilSeparateNoWrap;
487 }
488 passCount = 2;
489 drawFace[0] = GrDrawFace::kBoth;
490 } else {
491 if (fStencilWrapOps) {
492 passes[0] = &gWindSingleStencilWithWrapInc;
493 passes[1] = &gWindSingleStencilWithWrapDec;
494 } else {
495 passes[0] = &gWindSingleStencilNoWrapInc;
496 passes[1] = &gWindSingleStencilNoWrapDec;
497 }
498 // which is cw and which is ccw is arbitrary.
499 drawFace[0] = GrDrawFace::kCW;
500 drawFace[1] = GrDrawFace::kCCW;
501 passCount = 3;
502 }
503 if (stencilOnly) {
504 lastPassIsBounds = false;
505 --passCount;
506 } else {
507 lastPassIsBounds = true;
508 drawFace[passCount-1] = GrDrawFace::kBoth;
509 if (reverse) {
510 passes[passCount-1] = &gInvWindColorPass;
511 } else {
512 passes[passCount-1] = &gWindColorPass;
513 }
514 }
515 break;
516 default:
517 SkDEBUGFAIL("Unknown path fFill!");
518 return false;
519 }
520 }
521 }
522
523 SkScalar tol = GrPathUtils::kDefaultTolerance;
524 SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, path.getBounds());
525
526 SkRect devBounds;
527 GetPathDevBounds(path, renderTargetContext->width(), renderTargetContext->height(), viewMatrix,
528 &devBounds);
529
530 for (int p = 0; p < passCount; ++p) {
531 if (lastPassIsBounds && (p == passCount-1)) {
532 SkRect bounds;
533 SkMatrix localMatrix = SkMatrix::I();
534 if (reverse) {
535 // draw over the dev bounds (which will be the whole dst surface for inv fill).
536 bounds = devBounds;
537 SkMatrix vmi;
538 // mapRect through persp matrix may not be correct
539 if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) {
540 vmi.mapRect(&bounds);
541 } else {
542 if (!viewMatrix.invert(&localMatrix)) {
543 return false;
544 }
545 }
546 } else {
547 bounds = path.getBounds();
548 }
549 const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() :
550 viewMatrix;
551 std::unique_ptr<GrMeshDrawOp> op(GrRectOpFactory::MakeNonAAFill(
552 paint.getColor(), viewM, bounds, nullptr, &localMatrix));
553
554 SkASSERT(GrDrawFace::kBoth == drawFace[p]);
555 GrPipelineBuilder pipelineBuilder(std::move(paint), aaType);
556 pipelineBuilder.setDrawFace(drawFace[p]);
557 pipelineBuilder.setUserStencil(passes[p]);
558 renderTargetContext->addMeshDrawOp(pipelineBuilder, clip, std::move(op));
559 } else {
560 std::unique_ptr<GrMeshDrawOp> op =
561 DefaultPathOp::Make(paint.getColor(), path, srcSpaceTol, newCoverage,
562 viewMatrix, isHairline, devBounds);
563 bool stencilPass = stencilOnly || passCount > 1;
564 GrPaint::MoveOrNew passPaint(paint, stencilPass);
565 if (stencilPass) {
566 passPaint.paint().setXPFactory(GrDisableColorXPFactory::Get());
567 }
568 GrPipelineBuilder pipelineBuilder(std::move(passPaint), aaType);
569 pipelineBuilder.setDrawFace(drawFace[p]);
570 pipelineBuilder.setUserStencil(passes[p]);
571 renderTargetContext->addMeshDrawOp(pipelineBuilder, clip, std::move(op));
572 }
573 }
574 return true;
575 }
576
onCanDrawPath(const CanDrawPathArgs & args) const577 bool GrDefaultPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
578 // This can draw any path with any simple fill style but doesn't do coverage-based antialiasing.
579 return GrAAType::kCoverage != args.fAAType &&
580 (args.fShape->style().isSimpleFill() ||
581 IsStrokeHairlineOrEquivalent(args.fShape->style(), *args.fViewMatrix, nullptr));
582 }
583
onDrawPath(const DrawPathArgs & args)584 bool GrDefaultPathRenderer::onDrawPath(const DrawPathArgs& args) {
585 GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
586 "GrDefaultPathRenderer::onDrawPath");
587 return this->internalDrawPath(args.fRenderTargetContext,
588 std::move(args.fPaint),
589 args.fAAType,
590 *args.fUserStencilSettings,
591 *args.fClip,
592 *args.fViewMatrix,
593 *args.fShape,
594 false);
595 }
596
onStencilPath(const StencilPathArgs & args)597 void GrDefaultPathRenderer::onStencilPath(const StencilPathArgs& args) {
598 GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
599 "GrDefaultPathRenderer::onStencilPath");
600 SkASSERT(!args.fShape->inverseFilled());
601
602 GrPaint paint;
603 paint.setXPFactory(GrDisableColorXPFactory::Get());
604
605 this->internalDrawPath(args.fRenderTargetContext, std::move(paint), args.fAAType,
606 GrUserStencilSettings::kUnused, *args.fClip, *args.fViewMatrix,
607 *args.fShape, true);
608 }
609
610 ///////////////////////////////////////////////////////////////////////////////////////////////////
611
612 #if GR_TEST_UTILS
613
DRAW_OP_TEST_DEFINE(DefaultPathOp)614 DRAW_OP_TEST_DEFINE(DefaultPathOp) {
615 GrColor color = GrRandomColor(random);
616 SkMatrix viewMatrix = GrTest::TestMatrix(random);
617
618 // For now just hairlines because the other types of draws require two ops.
619 // TODO we should figure out a way to combine the stencil and cover steps into one op.
620 GrStyle style(SkStrokeRec::kHairline_InitStyle);
621 SkPath path = GrTest::TestPath(random);
622
623 // Compute srcSpaceTol
624 SkRect bounds = path.getBounds();
625 SkScalar tol = GrPathUtils::kDefaultTolerance;
626 SkScalar srcSpaceTol = GrPathUtils::scaleToleranceToSrc(tol, viewMatrix, bounds);
627
628 viewMatrix.mapRect(&bounds);
629 uint8_t coverage = GrRandomCoverage(random);
630 return DefaultPathOp::Make(color, path, srcSpaceTol, coverage, viewMatrix, true, bounds);
631 }
632
633 #endif
634