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