• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 Google LLC.
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/PathInnerTriangulateOp.h"
9 
10 #include "src/gpu/GrEagerVertexAllocator.h"
11 #include "src/gpu/GrGpu.h"
12 #include "src/gpu/GrOpFlushState.h"
13 #include "src/gpu/GrRecordingContextPriv.h"
14 #include "src/gpu/GrResourceProvider.h"
15 #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
16 #include "src/gpu/tessellate/PathCurveTessellator.h"
17 #include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
18 
19 namespace {
20 
21 // Fills an array of convex hulls surrounding 4-point cubic or conic instances. This shader is used
22 // for the "cover" pass after the curves have been fully stencilled.
23 class HullShader : public GrPathTessellationShader {
24 public:
HullShader(const SkMatrix & viewMatrix,SkPMColor4f color,const GrShaderCaps & shaderCaps)25     HullShader(const SkMatrix& viewMatrix, SkPMColor4f color, const GrShaderCaps& shaderCaps)
26             : GrPathTessellationShader(kTessellate_HullShader_ClassID,
27                                        GrPrimitiveType::kTriangleStrip, 0, viewMatrix, color,
28                                        skgpu::PatchAttribs::kNone) {
29         fInstanceAttribs.emplace_back("p01", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
30         fInstanceAttribs.emplace_back("p23", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
31         if (!shaderCaps.infinitySupport()) {
32             // A conic curve is written out with p3=[w,Infinity], but GPUs that don't support
33             // infinity can't detect this. On these platforms we also write out an extra float with
34             // each patch that explicitly tells the shader what type of curve it is.
35             fInstanceAttribs.emplace_back("curveType", kFloat_GrVertexAttribType, kFloat_GrSLType);
36         }
37         this->setInstanceAttributes(fInstanceAttribs.data(), fInstanceAttribs.count());
38         SkASSERT(fInstanceAttribs.count() <= kMaxInstanceAttribCount);
39 
40         if (!shaderCaps.vertexIDSupport()) {
41             constexpr static Attribute kVertexIdxAttrib("vertexidx", kFloat_GrVertexAttribType,
42                                                         kFloat_GrSLType);
43             this->setVertexAttributes(&kVertexIdxAttrib, 1);
44         }
45     }
46 
maxTessellationSegments(const GrShaderCaps &) const47     int maxTessellationSegments(const GrShaderCaps&) const override { SkUNREACHABLE; }
48 
49 private:
name() const50     const char* name() const final { return "tessellate_HullShader"; }
addToKey(const GrShaderCaps &,GrProcessorKeyBuilder *) const51     void addToKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
52     std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const final;
53 
54     constexpr static int kMaxInstanceAttribCount = 3;
55     SkSTArray<kMaxInstanceAttribCount, Attribute> fInstanceAttribs;
56 };
57 
makeProgramImpl(const GrShaderCaps &) const58 std::unique_ptr<GrGeometryProcessor::ProgramImpl> HullShader::makeProgramImpl(
59         const GrShaderCaps&) const {
60     class Impl : public GrPathTessellationShader::Impl {
61         void emitVertexCode(const GrShaderCaps& shaderCaps,
62                             const GrPathTessellationShader&,
63                             GrGLSLVertexBuilder* v,
64                             GrGLSLVaryingHandler*,
65                             GrGPArgs* gpArgs) override {
66             if (shaderCaps.infinitySupport()) {
67                 v->insertFunction(R"(
68                 bool is_conic_curve() { return isinf(p23.w); }
69                 bool is_non_triangular_conic_curve() {
70                     // We consider a conic non-triangular as long as its weight isn't infinity.
71                     // NOTE: "isinf == false" works on Mac Radeon GLSL; "!isinf" can get the wrong
72                     // answer.
73                     return isinf(p23.z) == false;
74                 })");
75             } else {
76                 v->insertFunction(SkStringPrintf(R"(
77                 bool is_conic_curve() { return curveType != %g; })", kCubicCurveType).c_str());
78                 v->insertFunction(SkStringPrintf(R"(
79                 bool is_non_triangular_conic_curve() {
80                     return curveType == %g;
81                 })", kConicCurveType).c_str());
82             }
83             v->codeAppend(R"(
84             float2 p0=p01.xy, p1=p01.zw, p2=p23.xy, p3=p23.zw;
85             if (is_conic_curve()) {
86                 // Conics are 3 points, with the weight in p3.
87                 float w = p3.x;
88                 p3 = p2;  // Duplicate the endpoint for shared code that also runs on cubics.
89                 if (is_non_triangular_conic_curve()) {
90                     // Convert the points to a trapeziodal hull that circumcscribes the conic.
91                     float2 p1w = p1 * w;
92                     float T = .51;  // Bias outward a bit to ensure we cover the outermost samples.
93                     float2 c1 = mix(p0, p1w, T);
94                     float2 c2 = mix(p2, p1w, T);
95                     float iw = 1 / mix(1, w, T);
96                     p2 = c2 * iw;
97                     p1 = c1 * iw;
98                 }
99             }
100 
101             // Translate the points to v0..3 where v0=0.
102             float2 v1 = p1 - p0;
103             float2 v2 = p2 - p0;
104             float2 v3 = p3 - p0;
105 
106             // Reorder the points so v2 bisects v1 and v3.
107             if (sign(cross(v2, v1)) == sign(cross(v2, v3))) {
108                 float2 tmp = p2;
109                 if (sign(cross(v1, v2)) != sign(cross(v1, v3))) {
110                     p2 = p1;  // swap(p2, p1)
111                     p1 = tmp;
112                 } else {
113                     p2 = p3;  // swap(p2, p3)
114                     p3 = tmp;
115                 }
116             })");
117 
118             if (shaderCaps.vertexIDSupport()) {
119                 // If we don't have sk_VertexID support then "vertexidx" already came in as a
120                 // vertex attrib.
121                 v->codeAppend(R"(
122                 // sk_VertexID comes in fan order. Convert to strip order.
123                 int vertexidx = sk_VertexID;
124                 vertexidx ^= vertexidx >> 1;)");
125             }
126 
127             v->codeAppend(R"(
128             // Find the "turn direction" of each corner and net turn direction.
129             float vertexdir = 0;
130             float netdir = 0;
131             float2 prev, next;
132             float dir;
133             float2 localcoord;
134             float2 nextcoord;)");
135 
136             for (int i = 0; i < 4; ++i) {
137                 v->codeAppendf(R"(
138                 prev = p%i - p%i;)", i, (i + 3) % 4);
139                 v->codeAppendf(R"(
140                 next = p%i - p%i;)", (i + 1) % 4, i);
141                 v->codeAppendf(R"(
142                 dir = sign(cross(prev, next));
143                 if (vertexidx == %i) {
144                     vertexdir = dir;
145                     localcoord = p%i;
146                     nextcoord = p%i;
147                 }
148                 netdir += dir;)", i, i, (i + 1) % 4);
149             }
150 
151             v->codeAppend(R"(
152             // Remove the non-convex vertex, if any.
153             if (vertexdir != sign(netdir)) {
154                 localcoord = nextcoord;
155             }
156 
157             float2 vertexpos = AFFINE_MATRIX * localcoord + TRANSLATE;)");
158             gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "localcoord");
159             gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
160         }
161     };
162     return std::make_unique<Impl>();
163 }
164 
165 }  // anonymous namespace
166 
167 namespace skgpu::v1 {
168 
visitProxies(const GrVisitProxyFunc & func) const169 void PathInnerTriangulateOp::visitProxies(const GrVisitProxyFunc& func) const {
170     if (fPipelineForFills) {
171         fPipelineForFills->visitProxies(func);
172     } else {
173         fProcessors.visitProxies(func);
174     }
175 }
176 
fixedFunctionFlags() const177 GrDrawOp::FixedFunctionFlags PathInnerTriangulateOp::fixedFunctionFlags() const {
178     auto flags = FixedFunctionFlags::kUsesStencil;
179     if (GrAAType::kNone != fAAType) {
180         flags |= FixedFunctionFlags::kUsesHWAA;
181     }
182     return flags;
183 }
184 
finalize(const GrCaps & caps,const GrAppliedClip * clip,GrClampType clampType)185 GrProcessorSet::Analysis PathInnerTriangulateOp::finalize(const GrCaps& caps,
186                                                           const GrAppliedClip* clip,
187                                                           GrClampType clampType) {
188     return fProcessors.finalize(fColor, GrProcessorAnalysisCoverage::kNone, clip, nullptr, caps,
189                                 clampType, &fColor);
190 }
191 
pushFanStencilProgram(const GrTessellationShader::ProgramArgs & args,const GrPipeline * pipelineForStencils,const GrUserStencilSettings * stencil)192 void PathInnerTriangulateOp::pushFanStencilProgram(const GrTessellationShader::ProgramArgs& args,
193                                                    const GrPipeline* pipelineForStencils,
194                                                    const GrUserStencilSettings* stencil) {
195     SkASSERT(pipelineForStencils);
196     auto shader = GrPathTessellationShader::MakeSimpleTriangleShader(args.fArena, fViewMatrix,
197                                                                      SK_PMColor4fTRANSPARENT);
198     fFanPrograms.push_back(GrTessellationShader::MakeProgram(args, shader, pipelineForStencils,
199                                                              stencil)); }
200 
pushFanFillProgram(const GrTessellationShader::ProgramArgs & args,const GrUserStencilSettings * stencil)201 void PathInnerTriangulateOp::pushFanFillProgram(const GrTessellationShader::ProgramArgs& args,
202                                                 const GrUserStencilSettings* stencil) {
203     SkASSERT(fPipelineForFills);
204     auto shader = GrPathTessellationShader::MakeSimpleTriangleShader(args.fArena, fViewMatrix,
205                                                                      fColor);
206     fFanPrograms.push_back(GrTessellationShader::MakeProgram(args, shader, fPipelineForFills,
207                                                              stencil));
208 }
209 
prePreparePrograms(const GrTessellationShader::ProgramArgs & args,GrAppliedClip && appliedClip)210 void PathInnerTriangulateOp::prePreparePrograms(const GrTessellationShader::ProgramArgs& args,
211                                                 GrAppliedClip&& appliedClip) {
212     SkASSERT(!fFanTriangulator);
213     SkASSERT(!fFanPolys);
214     SkASSERT(!fPipelineForFills);
215     SkASSERT(!fTessellator);
216     SkASSERT(!fStencilCurvesProgram);
217     SkASSERT(fFanPrograms.empty());
218     SkASSERT(!fCoverHullsProgram);
219 
220     if (fPath.countVerbs() <= 0) {
221         return;
222     }
223 
224     // If using wireframe, we have to fall back on a standard Redbook "stencil then cover" algorithm
225     // instead of bypassing the stencil buffer to fill the fan directly.
226     bool forceRedbookStencilPass =
227             (fPathFlags & (FillPathFlags::kStencilOnly | FillPathFlags::kWireframe));
228     bool doFill = !(fPathFlags & FillPathFlags::kStencilOnly);
229 
230     bool isLinear;
231     fFanTriangulator = args.fArena->make<GrInnerFanTriangulator>(fPath, args.fArena);
232     fFanPolys = fFanTriangulator->pathToPolys(&fFanBreadcrumbs, &isLinear);
233 
234     // Create a pipeline for stencil passes if needed.
235     const GrPipeline* pipelineForStencils = nullptr;
236     if (forceRedbookStencilPass || !isLinear) {  // Curves always get stencilled.
237         auto pipelineFlags = (fPathFlags & FillPathFlags::kWireframe)
238                 ? GrPipeline::InputFlags::kWireframe
239                 : GrPipeline::InputFlags::kNone;
240         pipelineForStencils = GrPathTessellationShader::MakeStencilOnlyPipeline(
241                 args, fAAType, appliedClip.hardClip(), pipelineFlags);
242     }
243 
244     // Create a pipeline for fill passes if needed.
245     if (doFill) {
246         fPipelineForFills = GrTessellationShader::MakePipeline(args, fAAType,
247                                                                std::move(appliedClip),
248                                                                std::move(fProcessors));
249     }
250 
251     // Pass 1: Tessellate the outer curves into the stencil buffer.
252     if (!isLinear) {
253         fTessellator = PathCurveTessellator::Make(args.fArena,
254                                                   args.fCaps->shaderCaps()->infinitySupport());
255         auto* tessShader = GrPathTessellationShader::Make(args.fArena,
256                                                           fViewMatrix,
257                                                           SK_PMColor4fTRANSPARENT,
258                                                           fPath.countVerbs(),
259                                                           *pipelineForStencils,
260                                                           fTessellator->patchAttribs(),
261                                                           *args.fCaps);
262         const GrUserStencilSettings* stencilPathSettings =
263                 GrPathTessellationShader::StencilPathSettings(GrFillRuleForSkPath(fPath));
264         fStencilCurvesProgram = GrTessellationShader::MakeProgram(args,
265                                                                   tessShader,
266                                                                   pipelineForStencils,
267                                                                   stencilPathSettings);
268     }
269 
270     // Pass 2: Fill the path's inner fan with a stencil test against the curves.
271     if (fFanPolys) {
272         if (forceRedbookStencilPass) {
273             // Use a standard Redbook "stencil then cover" algorithm instead of bypassing the
274             // stencil buffer to fill the fan directly.
275             const GrUserStencilSettings* stencilPathSettings =
276                     GrPathTessellationShader::StencilPathSettings(GrFillRuleForSkPath(fPath));
277             this->pushFanStencilProgram(args, pipelineForStencils, stencilPathSettings);
278             if (doFill) {
279                 this->pushFanFillProgram(args,
280                                          GrPathTessellationShader::TestAndResetStencilSettings());
281             }
282         } else if (isLinear) {
283             // There are no outer curves! Ignore stencil and fill the path directly.
284             SkASSERT(!pipelineForStencils);
285             this->pushFanFillProgram(args, &GrUserStencilSettings::kUnused);
286         } else if (!fPipelineForFills->hasStencilClip()) {
287             // These are a twist on the standard Redbook stencil settings that allow us to fill the
288             // inner polygon directly to the final render target. By the time these programs
289             // execute, the outer curves will already be stencilled in. So if the stencil value is
290             // zero, then it means the sample in question is not affected by any curves and we can
291             // fill it in directly. If the stencil value is nonzero, then we don't fill and instead
292             // continue the standard Redbook counting process.
293             constexpr static GrUserStencilSettings kFillOrIncrDecrStencil(
294                 GrUserStencilSettings::StaticInitSeparate<
295                     0x0000,                       0x0000,
296                     GrUserStencilTest::kEqual,    GrUserStencilTest::kEqual,
297                     0xffff,                       0xffff,
298                     GrUserStencilOp::kKeep,       GrUserStencilOp::kKeep,
299                     GrUserStencilOp::kIncWrap,    GrUserStencilOp::kDecWrap,
300                     0xffff,                       0xffff>());
301 
302             constexpr static GrUserStencilSettings kFillOrInvertStencil(
303                 GrUserStencilSettings::StaticInit<
304                     0x0000,
305                     GrUserStencilTest::kEqual,
306                     0xffff,
307                     GrUserStencilOp::kKeep,
308                     // "Zero" instead of "Invert" because the fan only touches any given pixel once.
309                     GrUserStencilOp::kZero,
310                     0xffff>());
311 
312             auto* stencil = (fPath.getFillType() == SkPathFillType::kWinding)
313                     ? &kFillOrIncrDecrStencil
314                     : &kFillOrInvertStencil;
315             this->pushFanFillProgram(args, stencil);
316         } else {
317             // This is the same idea as above, but we use two passes instead of one because there is
318             // a stencil clip. The stencil test isn't expressive enough to do the above tests and
319             // also check the clip bit in a single pass.
320             constexpr static GrUserStencilSettings kFillIfZeroAndInClip(
321                 GrUserStencilSettings::StaticInit<
322                     0x0000,
323                     GrUserStencilTest::kEqualIfInClip,
324                     0xffff,
325                     GrUserStencilOp::kKeep,
326                     GrUserStencilOp::kKeep,
327                     0xffff>());
328 
329             constexpr static GrUserStencilSettings kIncrDecrStencilIfNonzero(
330                 GrUserStencilSettings::StaticInitSeparate<
331                     0x0000,                         0x0000,
332                     // No need to check the clip because the previous stencil pass will have only
333                     // written to samples already inside the clip.
334                     GrUserStencilTest::kNotEqual,   GrUserStencilTest::kNotEqual,
335                     0xffff,                         0xffff,
336                     GrUserStencilOp::kIncWrap,      GrUserStencilOp::kDecWrap,
337                     GrUserStencilOp::kKeep,         GrUserStencilOp::kKeep,
338                     0xffff,                         0xffff>());
339 
340             constexpr static GrUserStencilSettings kInvertStencilIfNonZero(
341                 GrUserStencilSettings::StaticInit<
342                     0x0000,
343                     // No need to check the clip because the previous stencil pass will have only
344                     // written to samples already inside the clip.
345                     GrUserStencilTest::kNotEqual,
346                     0xffff,
347                     // "Zero" instead of "Invert" because the fan only touches any given pixel once.
348                     GrUserStencilOp::kZero,
349                     GrUserStencilOp::kKeep,
350                     0xffff>());
351 
352             // Pass 2a: Directly fill fan samples whose stencil values (from curves) are zero.
353             this->pushFanFillProgram(args, &kFillIfZeroAndInClip);
354 
355             // Pass 2b: Redbook counting on fan samples whose stencil values (from curves) != 0.
356             auto* stencil = (fPath.getFillType() == SkPathFillType::kWinding)
357                     ? &kIncrDecrStencilIfNonzero
358                     : &kInvertStencilIfNonZero;
359             this->pushFanStencilProgram(args, pipelineForStencils, stencil);
360         }
361     }
362 
363     // Pass 3: Draw convex hulls around each curve.
364     if (doFill && !isLinear) {
365         // By the time this program executes, every pixel will be filled in except the ones touched
366         // by curves. We issue a final cover pass over the curves by drawing their convex hulls.
367         // This will fill in any remaining samples and reset the stencil values back to zero.
368         SkASSERT(fTessellator);
369         auto* hullShader = args.fArena->make<HullShader>(fViewMatrix, fColor,
370                                                          *args.fCaps->shaderCaps());
371         fCoverHullsProgram = GrTessellationShader::MakeProgram(
372                 args, hullShader, fPipelineForFills,
373                 GrPathTessellationShader::TestAndResetStencilSettings());
374     }
375 }
376 
onPrePrepare(GrRecordingContext * context,const GrSurfaceProxyView & writeView,GrAppliedClip * clip,const GrDstProxyView & dstProxyView,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)377 void PathInnerTriangulateOp::onPrePrepare(GrRecordingContext* context,
378                                           const GrSurfaceProxyView& writeView,
379                                           GrAppliedClip* clip,
380                                           const GrDstProxyView& dstProxyView,
381                                           GrXferBarrierFlags renderPassXferBarriers,
382                                           GrLoadOp colorLoadOp) {
383     // DMSAA is not supported on DDL.
384     bool usesMSAASurface = writeView.asRenderTargetProxy()->numSamples() > 1;
385     this->prePreparePrograms({context->priv().recordTimeAllocator(), writeView, usesMSAASurface,
386                              &dstProxyView, renderPassXferBarriers, colorLoadOp,
387                              context->priv().caps()},
388                              (clip) ? std::move(*clip) : GrAppliedClip::Disabled());
389     if (fStencilCurvesProgram) {
390         context->priv().recordProgramInfo(fStencilCurvesProgram);
391     }
392     for (const GrProgramInfo* fanProgram : fFanPrograms) {
393         context->priv().recordProgramInfo(fanProgram);
394     }
395     if (fCoverHullsProgram) {
396         context->priv().recordProgramInfo(fCoverHullsProgram);
397     }
398 }
399 
400 GR_DECLARE_STATIC_UNIQUE_KEY(gHullVertexBufferKey);
401 
onPrepare(GrOpFlushState * flushState)402 void PathInnerTriangulateOp::onPrepare(GrOpFlushState* flushState) {
403     const GrCaps& caps = flushState->caps();
404 
405     if (!fFanTriangulator) {
406         this->prePreparePrograms({flushState->allocator(), flushState->writeView(),
407                                  flushState->usesMSAASurface(), &flushState->dstProxyView(),
408                                  flushState->renderPassBarriers(), flushState->colorLoadOp(),
409                                  &caps}, flushState->detachAppliedClip());
410         if (!fFanTriangulator) {
411             return;
412         }
413     }
414 
415     if (fFanPolys) {
416         GrEagerDynamicVertexAllocator alloc(flushState, &fFanBuffer, &fBaseFanVertex);
417         fFanVertexCount = fFanTriangulator->polysToTriangles(fFanPolys, &alloc, &fFanBreadcrumbs);
418     }
419 
420     if (fTessellator) {
421         int patchPreallocCount = fFanBreadcrumbs.count() +
422                                  fTessellator->patchPreallocCount(fPath.countVerbs());
423         SkASSERT(patchPreallocCount);  // Otherwise fTessellator should be null.
424 
425         PatchWriter patchWriter(flushState, fTessellator, patchPreallocCount);
426 
427         // Write out breadcrumb triangles. This must be called after polysToTriangles() in order for
428         // fFanBreadcrumbs to be complete.
429         SkDEBUGCODE(int breadcrumbCount = 0;)
430         for (const auto* tri = fFanBreadcrumbs.head(); tri; tri = tri->fNext) {
431             SkDEBUGCODE(++breadcrumbCount;)
432             auto p0 = float2::Load(tri->fPts);
433             auto p1 = float2::Load(tri->fPts + 1);
434             auto p2 = float2::Load(tri->fPts + 2);
435             if (skvx::any((p0 == p1) & (p1 == p2))) {
436                 // Cull completely horizontal or vertical triangles. GrTriangulator can't always
437                 // get these breadcrumb edges right when they run parallel to the sweep
438                 // direction because their winding is undefined by its current definition.
439                 // FIXME(skia:12060): This seemed safe, but if there is a view matrix it will
440                 // introduce T-junctions.
441                 continue;
442             }
443             PatchWriter::TrianglePatch(patchWriter) << p0 << p1 << p2;
444         }
445         SkASSERT(breadcrumbCount == fFanBreadcrumbs.count());
446 
447         // Write out the curves.
448         auto tessShader = &fStencilCurvesProgram->geomProc().cast<GrPathTessellationShader>();
449         fTessellator->writePatches(patchWriter,
450                                    tessShader->maxTessellationSegments(*caps.shaderCaps()),
451                                    tessShader->viewMatrix(),
452                                    {SkMatrix::I(), fPath, SK_PMColor4fTRANSPARENT});
453 
454         if (!tessShader->willUseTessellationShaders()) {
455             fTessellator->prepareFixedCountBuffers(flushState);
456         }
457     }
458 
459     if (!caps.shaderCaps()->vertexIDSupport()) {
460         constexpr static float kStripOrderIDs[4] = {0, 1, 3, 2};
461 
462         GR_DEFINE_STATIC_UNIQUE_KEY(gHullVertexBufferKey);
463 
464         fHullVertexBufferIfNoIDSupport = flushState->resourceProvider()->findOrMakeStaticBuffer(
465                 GrGpuBufferType::kVertex, sizeof(kStripOrderIDs), kStripOrderIDs,
466                 gHullVertexBufferKey);
467     }
468 }
469 
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)470 void PathInnerTriangulateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
471     if (fCoverHullsProgram &&
472         fCoverHullsProgram->geomProc().hasVertexAttributes() &&
473         !fHullVertexBufferIfNoIDSupport) {
474         return;
475     }
476 
477     if (fStencilCurvesProgram) {
478         SkASSERT(fTessellator);
479         flushState->bindPipelineAndScissorClip(*fStencilCurvesProgram, this->bounds());
480         fTessellator->draw(flushState,
481                            fStencilCurvesProgram->geomProc().willUseTessellationShaders());
482         if (flushState->caps().requiresManualFBBarrierAfterTessellatedStencilDraw()) {
483             flushState->gpu()->insertManualFramebufferBarrier();  // http://skbug.com/9739
484         }
485     }
486 
487     // Allocation of the fan vertex buffer may have failed but we already pushed back fan programs.
488     if (fFanBuffer) {
489         for (const GrProgramInfo* fanProgram : fFanPrograms) {
490             flushState->bindPipelineAndScissorClip(*fanProgram, this->bounds());
491             flushState->bindTextures(fanProgram->geomProc(), nullptr, fanProgram->pipeline());
492             flushState->bindBuffers(nullptr, nullptr, fFanBuffer);
493             flushState->draw(fFanVertexCount, fBaseFanVertex);
494         }
495     }
496 
497     if (fCoverHullsProgram) {
498         SkASSERT(fTessellator);
499         flushState->bindPipelineAndScissorClip(*fCoverHullsProgram, this->bounds());
500         flushState->bindTextures(fCoverHullsProgram->geomProc(), nullptr, *fPipelineForFills);
501         fTessellator->drawHullInstances(flushState, fHullVertexBufferIfNoIDSupport);
502     }
503 }
504 
505 } // namespace skgpu::v1
506