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