1 /*
2 * Copyright 2020 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/StrokeTessellateOp.h"
9
10 #include "src/core/SkMathPriv.h"
11 #include "src/core/SkPathPriv.h"
12 #include "src/gpu/GrAppliedClip.h"
13 #include "src/gpu/GrOpFlushState.h"
14 #include "src/gpu/GrRecordingContextPriv.h"
15 #include "src/gpu/tessellate/StrokeFixedCountTessellator.h"
16 #include "src/gpu/tessellate/StrokeHardwareTessellator.h"
17 #include "src/gpu/tessellate/shaders/GrStrokeTessellationShader.h"
18
19 namespace {
20
can_use_hardware_tessellation(int numVerbs,const GrPipeline & pipeline,const GrCaps & caps)21 bool can_use_hardware_tessellation(int numVerbs, const GrPipeline& pipeline, const GrCaps& caps) {
22 if (!caps.shaderCaps()->tessellationSupport() ||
23 !caps.shaderCaps()->infinitySupport() /* The hw tessellation shaders use infinity. */) {
24 return false;
25 }
26 if (pipeline.usesLocalCoords()) {
27 // Our back door for HW tessellation shaders isn't currently capable of passing varyings to
28 // the fragment shader, so if the processors have varyings, we need to use instanced draws
29 // instead.
30 return false;
31 }
32 // Only use hardware tessellation if we're drawing a somewhat large number of verbs. Otherwise
33 // we seem to be better off using instanced draws.
34 return numVerbs >= caps.minStrokeVerbsForHwTessellation();
35 }
36
37 } // anonymous namespace
38
39 namespace skgpu::v1 {
40
StrokeTessellateOp(GrAAType aaType,const SkMatrix & viewMatrix,const SkPath & path,const SkStrokeRec & stroke,GrPaint && paint)41 StrokeTessellateOp::StrokeTessellateOp(GrAAType aaType, const SkMatrix& viewMatrix,
42 const SkPath& path, const SkStrokeRec& stroke,
43 GrPaint&& paint)
44 : GrDrawOp(ClassID())
45 , fAAType(aaType)
46 , fViewMatrix(viewMatrix)
47 , fPathStrokeList(path, stroke, paint.getColor4f())
48 , fTotalCombinedVerbCnt(path.countVerbs())
49 , fProcessors(std::move(paint)) {
50 if (!this->headColor().fitsInBytes()) {
51 fPatchAttribs |= PatchAttribs::kWideColorIfEnabled;
52 }
53 SkRect devBounds = path.getBounds();
54 if (!this->headStroke().isHairlineStyle()) {
55 // Non-hairlines inflate in local path space (pre-transform).
56 float r = stroke.getInflationRadius();
57 devBounds.outset(r, r);
58 }
59 viewMatrix.mapRect(&devBounds, devBounds);
60 if (this->headStroke().isHairlineStyle()) {
61 // Hairlines inflate in device space (post-transform).
62 float r = SkStrokeRec::GetInflationRadius(stroke.getJoin(), stroke.getMiter(),
63 stroke.getCap(), 1);
64 devBounds.outset(r, r);
65 }
66 this->setBounds(devBounds, HasAABloat::kNo, IsHairline::kNo);
67 }
68
visitProxies(const GrVisitProxyFunc & func) const69 void StrokeTessellateOp::visitProxies(const GrVisitProxyFunc& func) const {
70 if (fFillProgram) {
71 fFillProgram->visitFPProxies(func);
72 } else if (fStencilProgram) {
73 fStencilProgram->visitFPProxies(func);
74 } else {
75 fProcessors.visitProxies(func);
76 }
77 }
78
finalize(const GrCaps & caps,const GrAppliedClip * clip,GrClampType clampType)79 GrProcessorSet::Analysis StrokeTessellateOp::finalize(const GrCaps& caps,
80 const GrAppliedClip* clip,
81 GrClampType clampType) {
82 // Make sure the finalize happens before combining. We might change fNeedsStencil here.
83 SkASSERT(fPathStrokeList.fNext == nullptr);
84 if (!caps.shaderCaps()->infinitySupport()) {
85 // The GPU can't infer curve type based in infinity, so we need to send in an attrib
86 // explicitly stating the curve type.
87 fPatchAttribs |= PatchAttribs::kExplicitCurveType;
88 }
89 const GrProcessorSet::Analysis& analysis = fProcessors.finalize(
90 this->headColor(), GrProcessorAnalysisCoverage::kNone, clip,
91 &GrUserStencilSettings::kUnused, caps, clampType, &this->headColor());
92 fNeedsStencil = !analysis.unaffectedByDstValue();
93 return analysis;
94 }
95
onCombineIfPossible(GrOp * grOp,SkArenaAlloc * alloc,const GrCaps & caps)96 GrOp::CombineResult StrokeTessellateOp::onCombineIfPossible(GrOp* grOp, SkArenaAlloc* alloc,
97 const GrCaps& caps) {
98 SkASSERT(grOp->classID() == this->classID());
99 auto* op = static_cast<StrokeTessellateOp*>(grOp);
100
101 // This must be called after finalize(). fNeedsStencil can change in finalize().
102 SkASSERT(fProcessors.isFinalized());
103 SkASSERT(op->fProcessors.isFinalized());
104
105 if (fNeedsStencil ||
106 op->fNeedsStencil ||
107 fViewMatrix != op->fViewMatrix ||
108 fAAType != op->fAAType ||
109 fProcessors != op->fProcessors ||
110 this->headStroke().isHairlineStyle() != op->headStroke().isHairlineStyle()) {
111 return CombineResult::kCannotCombine;
112 }
113
114 auto combinedAttribs = fPatchAttribs | op->fPatchAttribs;
115 if (!(combinedAttribs & PatchAttribs::kStrokeParams) &&
116 !StrokeParams::StrokesHaveEqualParams(this->headStroke(), op->headStroke())) {
117 // The paths have different stroke properties. We will need to enable dynamic stroke if we
118 // still decide to combine them.
119 if (this->headStroke().isHairlineStyle()) {
120 return CombineResult::kCannotCombine; // Dynamic hairlines aren't supported.
121 }
122 combinedAttribs |= PatchAttribs::kStrokeParams;
123 }
124 if (!(combinedAttribs & PatchAttribs::kColor) && this->headColor() != op->headColor()) {
125 // The paths have different colors. We will need to enable dynamic color if we still decide
126 // to combine them.
127 combinedAttribs |= PatchAttribs::kColor;
128 }
129
130 // Don't actually enable new dynamic state on ops that already have lots of verbs.
131 constexpr static GrTFlagsMask<PatchAttribs> kDynamicStatesMask(PatchAttribs::kStrokeParams |
132 PatchAttribs::kColor);
133 PatchAttribs neededDynamicStates = combinedAttribs & kDynamicStatesMask;
134 if (neededDynamicStates != PatchAttribs::kNone) {
135 if (!this->shouldUseDynamicStates(neededDynamicStates) ||
136 !op->shouldUseDynamicStates(neededDynamicStates)) {
137 return CombineResult::kCannotCombine;
138 }
139 }
140
141 fPatchAttribs = combinedAttribs;
142
143 // Concat the op's PathStrokeList. Since the head element is allocated inside the op, we need to
144 // copy it.
145 auto* headCopy = alloc->make<PathStrokeList>(std::move(op->fPathStrokeList));
146 *fPathStrokeTail = headCopy;
147 fPathStrokeTail = (op->fPathStrokeTail == &op->fPathStrokeList.fNext) ? &headCopy->fNext
148 : op->fPathStrokeTail;
149
150 fTotalCombinedVerbCnt += op->fTotalCombinedVerbCnt;
151 return CombineResult::kMerged;
152 }
153
154 // Marks every stencil value as "1".
155 constexpr static GrUserStencilSettings kMarkStencil(
156 GrUserStencilSettings::StaticInit<
157 0x0001,
158 GrUserStencilTest::kLessIfInClip, // Match kTestAndResetStencil.
159 0x0000, // Always fail.
160 GrUserStencilOp::kZero,
161 GrUserStencilOp::kReplace,
162 0xffff>());
163
164 // Passes if the stencil value is nonzero. Also resets the stencil value to zero on pass. This is
165 // formulated to match kMarkStencil everywhere except the ref and compare mask. This will allow us
166 // to use the same pipeline for both stencil and fill if dynamic stencil state is supported.
167 constexpr static GrUserStencilSettings kTestAndResetStencil(
168 GrUserStencilSettings::StaticInit<
169 0x0000,
170 GrUserStencilTest::kLessIfInClip, // i.e., "not equal to zero, if in clip".
171 0x0001,
172 GrUserStencilOp::kZero,
173 GrUserStencilOp::kReplace,
174 0xffff>());
175
prePrepareTessellator(GrTessellationShader::ProgramArgs && args,GrAppliedClip && clip)176 void StrokeTessellateOp::prePrepareTessellator(GrTessellationShader::ProgramArgs&& args,
177 GrAppliedClip&& clip) {
178 SkASSERT(!fTessellator);
179 SkASSERT(!fFillProgram);
180 SkASSERT(!fStencilProgram);
181 // GrOp::setClippedBounds() should have been called by now.
182 SkASSERT(SkRect::MakeIWH(args.fWriteView.width(),
183 args.fWriteView.height()).contains(this->bounds()));
184
185 const GrCaps& caps = *args.fCaps;
186 SkArenaAlloc* arena = args.fArena;
187
188 auto* pipeline = GrTessellationShader::MakePipeline(args, fAAType, std::move(clip),
189 std::move(fProcessors));
190
191 GrStrokeTessellationShader::Mode shaderMode;
192 int maxParametricSegments_log2;
193 if (can_use_hardware_tessellation(fTotalCombinedVerbCnt, *pipeline, caps)) {
194 // Only use hardware tessellation if we're drawing a somewhat large number of verbs.
195 // Otherwise we seem to be better off using instanced draws.
196 fTessellator = arena->make<StrokeHardwareTessellator>(fPatchAttribs);
197 shaderMode = GrStrokeTessellationShader::Mode::kHardwareTessellation;
198 // This sets a limit on the number of binary search iterations inside the shader, so we
199 // round up to the next log2 to guarantee it makes enough.
200 maxParametricSegments_log2 = SkNextLog2(caps.shaderCaps()->maxTessellationSegments());
201 } else {
202 fTessellator = arena->make<StrokeFixedCountTessellator>(fPatchAttribs);
203 shaderMode = GrStrokeTessellationShader::Mode::kFixedCount;
204 maxParametricSegments_log2 = StrokeFixedCountTessellator::kMaxParametricSegments_log2;
205 }
206
207 fTessellationShader = args.fArena->make<GrStrokeTessellationShader>(*caps.shaderCaps(),
208 shaderMode,
209 fPatchAttribs,
210 fViewMatrix,
211 this->headStroke(),
212 this->headColor(),
213 maxParametricSegments_log2);
214
215 auto fillStencil = &GrUserStencilSettings::kUnused;
216 if (fNeedsStencil) {
217 fStencilProgram = GrTessellationShader::MakeProgram(args, fTessellationShader, pipeline,
218 &kMarkStencil);
219 fillStencil = &kTestAndResetStencil;
220 args.fXferBarrierFlags = GrXferBarrierFlags::kNone;
221 }
222
223 fFillProgram = GrTessellationShader::MakeProgram(args, fTessellationShader, pipeline,
224 fillStencil);
225 }
226
onPrePrepare(GrRecordingContext * context,const GrSurfaceProxyView & writeView,GrAppliedClip * clip,const GrDstProxyView & dstProxyView,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)227 void StrokeTessellateOp::onPrePrepare(GrRecordingContext* context,
228 const GrSurfaceProxyView& writeView, GrAppliedClip* clip,
229 const GrDstProxyView& dstProxyView,
230 GrXferBarrierFlags renderPassXferBarriers, GrLoadOp
231 colorLoadOp) {
232 // DMSAA is not supported on DDL.
233 bool usesMSAASurface = writeView.asRenderTargetProxy()->numSamples() > 1;
234 this->prePrepareTessellator({context->priv().recordTimeAllocator(), writeView, usesMSAASurface,
235 &dstProxyView, renderPassXferBarriers, colorLoadOp,
236 context->priv().caps()},
237 (clip) ? std::move(*clip) : GrAppliedClip::Disabled());
238 if (fStencilProgram) {
239 context->priv().recordProgramInfo(fStencilProgram);
240 }
241 if (fFillProgram) {
242 context->priv().recordProgramInfo(fFillProgram);
243 }
244 }
245
onPrepare(GrOpFlushState * flushState)246 void StrokeTessellateOp::onPrepare(GrOpFlushState* flushState) {
247 if (!fTessellator) {
248 this->prePrepareTessellator({flushState->allocator(), flushState->writeView(),
249 flushState->usesMSAASurface(), &flushState->dstProxyView(),
250 flushState->renderPassBarriers(), flushState->colorLoadOp(),
251 &flushState->caps()}, flushState->detachAppliedClip());
252 }
253 SkASSERT(fTessellator);
254 std::array<float, 2> matrixMinMaxScales;
255 if (!fViewMatrix.getMinMaxScales(matrixMinMaxScales.data())) {
256 matrixMinMaxScales.fill(1);
257 }
258 int fixedEdgeCount = fTessellator->prepare(flushState,
259 fViewMatrix,
260 matrixMinMaxScales,
261 &fPathStrokeList,
262 fTotalCombinedVerbCnt);
263 if (!fTessellationShader->willUseTessellationShaders()) {
264 fTessellationShader->setFixedCountNumTotalEdges(fixedEdgeCount);
265 }
266 }
267
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)268 void StrokeTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
269 if (fStencilProgram) {
270 flushState->bindPipelineAndScissorClip(*fStencilProgram, chainBounds);
271 flushState->bindTextures(fStencilProgram->geomProc(), nullptr, fStencilProgram->pipeline());
272 fTessellator->draw(flushState);
273 }
274 if (fFillProgram) {
275 flushState->bindPipelineAndScissorClip(*fFillProgram, chainBounds);
276 flushState->bindTextures(fFillProgram->geomProc(), nullptr, fFillProgram->pipeline());
277 fTessellator->draw(flushState);
278 }
279 }
280
281 } // namespace skgpu::v1
282