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