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/ganesh/ops/StrokeTessellateOp.h"
9
10 #include "include/core/SkColor.h"
11 #include "include/core/SkPath.h"
12 #include "include/core/SkRect.h"
13 #include "include/gpu/ganesh/GrRecordingContext.h"
14 #include "src/base/SkArenaAlloc.h"
15 #include "src/gpu/ganesh/GrAppliedClip.h"
16 #include "src/gpu/ganesh/GrCaps.h"
17 #include "src/gpu/ganesh/GrOpFlushState.h"
18 #include "src/gpu/ganesh/GrPaint.h"
19 #include "src/gpu/ganesh/GrProcessorAnalysis.h"
20 #include "src/gpu/ganesh/GrProgramInfo.h"
21 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
22 #include "src/gpu/ganesh/GrRenderTargetProxy.h"
23 #include "src/gpu/ganesh/GrShaderCaps.h"
24 #include "src/gpu/ganesh/GrSurfaceProxyView.h"
25 #include "src/gpu/ganesh/GrUserStencilSettings.h"
26 #include "src/gpu/ganesh/tessellate/GrStrokeTessellationShader.h"
27
28 #include <utility>
29
30 namespace skgpu::ganesh {
31
StrokeTessellateOp(GrAAType aaType,const SkMatrix & viewMatrix,const SkPath & path,const SkStrokeRec & stroke,GrPaint && paint)32 StrokeTessellateOp::StrokeTessellateOp(GrAAType aaType, const SkMatrix& viewMatrix,
33 const SkPath& path, const SkStrokeRec& stroke,
34 GrPaint&& paint)
35 : GrDrawOp(ClassID())
36 , fAAType(aaType)
37 , fViewMatrix(viewMatrix)
38 , fPathStrokeList(path, stroke, paint.getColor4f())
39 , fTotalCombinedVerbCnt(path.countVerbs())
40 , fProcessors(std::move(paint)) {
41 if (!this->headColor().fitsInBytes()) {
42 fPatchAttribs |= PatchAttribs::kWideColorIfEnabled;
43 }
44 SkRect devBounds = path.getBounds();
45 if (!this->headStroke().isHairlineStyle()) {
46 // Non-hairlines inflate in local path space (pre-transform).
47 float r = stroke.getInflationRadius();
48 devBounds.outset(r, r);
49 }
50 viewMatrix.mapRect(&devBounds, devBounds);
51 if (this->headStroke().isHairlineStyle()) {
52 // Hairlines inflate in device space (post-transform).
53 float r = SkStrokeRec::GetInflationRadius(stroke.getJoin(), stroke.getMiter(),
54 stroke.getCap(), 1);
55 devBounds.outset(r, r);
56 }
57 this->setBounds(devBounds, HasAABloat::kNo, IsHairline::kNo);
58 }
59
visitProxies(const GrVisitProxyFunc & func) const60 void StrokeTessellateOp::visitProxies(const GrVisitProxyFunc& func) const {
61 if (fFillProgram) {
62 fFillProgram->visitFPProxies(func);
63 } else if (fStencilProgram) {
64 fStencilProgram->visitFPProxies(func);
65 } else {
66 fProcessors.visitProxies(func);
67 }
68 }
69
finalize(const GrCaps & caps,const GrAppliedClip * clip,GrClampType clampType)70 GrProcessorSet::Analysis StrokeTessellateOp::finalize(const GrCaps& caps,
71 const GrAppliedClip* clip,
72 GrClampType clampType) {
73 // Make sure the finalize happens before combining. We might change fNeedsStencil here.
74 SkASSERT(fPathStrokeList.fNext == nullptr);
75 if (!caps.shaderCaps()->fInfinitySupport) {
76 // The GPU can't infer curve type based in infinity, so we need to send in an attrib
77 // explicitly stating the curve type.
78 fPatchAttribs |= PatchAttribs::kExplicitCurveType;
79 }
80 const GrProcessorSet::Analysis& analysis = fProcessors.finalize(
81 this->headColor(), GrProcessorAnalysisCoverage::kNone, clip,
82 &GrUserStencilSettings::kUnused, caps, clampType, &this->headColor());
83 fNeedsStencil = !analysis.unaffectedByDstValue();
84 return analysis;
85 }
86
onCombineIfPossible(GrOp * grOp,SkArenaAlloc * alloc,const GrCaps & caps)87 GrOp::CombineResult StrokeTessellateOp::onCombineIfPossible(GrOp* grOp, SkArenaAlloc* alloc,
88 const GrCaps& caps) {
89 SkASSERT(grOp->classID() == this->classID());
90 auto* op = static_cast<StrokeTessellateOp*>(grOp);
91
92 // This must be called after finalize(). fNeedsStencil can change in finalize().
93 SkASSERT(fProcessors.isFinalized());
94 SkASSERT(op->fProcessors.isFinalized());
95
96 if (fNeedsStencil ||
97 op->fNeedsStencil ||
98 fViewMatrix != op->fViewMatrix ||
99 fAAType != op->fAAType ||
100 fProcessors != op->fProcessors ||
101 this->headStroke().isHairlineStyle() != op->headStroke().isHairlineStyle()) {
102 return CombineResult::kCannotCombine;
103 }
104
105 auto combinedAttribs = fPatchAttribs | op->fPatchAttribs;
106 if (!(combinedAttribs & PatchAttribs::kStrokeParams) &&
107 !tess::StrokesHaveEqualParams(this->headStroke(), op->headStroke())) {
108 // The paths have different stroke properties. We will need to enable dynamic stroke if we
109 // still decide to combine them.
110 if (this->headStroke().isHairlineStyle()) {
111 return CombineResult::kCannotCombine; // Dynamic hairlines aren't supported.
112 }
113 combinedAttribs |= PatchAttribs::kStrokeParams;
114 }
115 if (!(combinedAttribs & PatchAttribs::kColor) && this->headColor() != op->headColor()) {
116 // The paths have different colors. We will need to enable dynamic color if we still decide
117 // to combine them.
118 combinedAttribs |= PatchAttribs::kColor;
119 }
120
121 // Don't actually enable new dynamic state on ops that already have lots of verbs.
122 constexpr static SkTFlagsMask<PatchAttribs> kDynamicStatesMask(PatchAttribs::kStrokeParams |
123 PatchAttribs::kColor);
124 PatchAttribs neededDynamicStates = combinedAttribs & kDynamicStatesMask;
125 if (neededDynamicStates != PatchAttribs::kNone) {
126 if (!this->shouldUseDynamicStates(neededDynamicStates) ||
127 !op->shouldUseDynamicStates(neededDynamicStates)) {
128 return CombineResult::kCannotCombine;
129 }
130 }
131
132 fPatchAttribs = combinedAttribs;
133
134 // Concat the op's PathStrokeList. Since the head element is allocated inside the op, we need to
135 // copy it.
136 auto* headCopy = alloc->make<PathStrokeList>(std::move(op->fPathStrokeList));
137 *fPathStrokeTail = headCopy;
138 fPathStrokeTail = (op->fPathStrokeTail == &op->fPathStrokeList.fNext) ? &headCopy->fNext
139 : op->fPathStrokeTail;
140
141 fTotalCombinedVerbCnt += op->fTotalCombinedVerbCnt;
142 return CombineResult::kMerged;
143 }
144
145 // Marks every stencil value as "1".
146 constexpr static GrUserStencilSettings kMarkStencil(
147 GrUserStencilSettings::StaticInit<
148 0x0001,
149 GrUserStencilTest::kLessIfInClip, // Match kTestAndResetStencil.
150 0x0000, // Always fail.
151 GrUserStencilOp::kZero,
152 GrUserStencilOp::kReplace,
153 0xffff>());
154
155 // Passes if the stencil value is nonzero. Also resets the stencil value to zero on pass. This is
156 // formulated to match kMarkStencil everywhere except the ref and compare mask. This will allow us
157 // to use the same pipeline for both stencil and fill if dynamic stencil state is supported.
158 constexpr static GrUserStencilSettings kTestAndResetStencil(
159 GrUserStencilSettings::StaticInit<
160 0x0000,
161 GrUserStencilTest::kLessIfInClip, // i.e., "not equal to zero, if in clip".
162 0x0001,
163 GrUserStencilOp::kZero,
164 GrUserStencilOp::kReplace,
165 0xffff>());
166
prePrepareTessellator(GrTessellationShader::ProgramArgs && args,GrAppliedClip && clip)167 void StrokeTessellateOp::prePrepareTessellator(GrTessellationShader::ProgramArgs&& args,
168 GrAppliedClip&& clip) {
169 SkASSERT(!fTessellator);
170 SkASSERT(!fFillProgram);
171 SkASSERT(!fStencilProgram);
172 // GrOp::setClippedBounds() should have been called by now.
173 SkASSERT(SkRect::MakeIWH(args.fWriteView.width(),
174 args.fWriteView.height()).contains(this->bounds()));
175
176 const GrCaps& caps = *args.fCaps;
177 SkArenaAlloc* arena = args.fArena;
178
179 auto* pipeline = GrTessellationShader::MakePipeline(args, fAAType, std::move(clip),
180 std::move(fProcessors));
181
182 fTessellator = arena->make<StrokeTessellator>(fPatchAttribs);
183 fTessellationShader = args.fArena->make<GrStrokeTessellationShader>(
184 *caps.shaderCaps(),
185 fPatchAttribs,
186 fViewMatrix,
187 this->headStroke(),
188 this->headColor());
189
190 auto fillStencil = &GrUserStencilSettings::kUnused;
191 if (fNeedsStencil) {
192 fStencilProgram = GrTessellationShader::MakeProgram(args, fTessellationShader, pipeline,
193 &kMarkStencil);
194 fillStencil = &kTestAndResetStencil;
195 // TODO: Currently if we have a texture barrier for a dst read it will get put in before
196 // both the stencil draw and the fill draw. In reality we only really need the barrier
197 // once to guard the reads of the color buffer in the fill from the previous writes. Maybe
198 // we can investigate how to remove one of these barriers but it is probably not something
199 // that is required a lot and thus the extra barrier shouldn't be too much of a perf hit to
200 // general Skia use.
201 }
202
203 fFillProgram = GrTessellationShader::MakeProgram(args, fTessellationShader, pipeline,
204 fillStencil);
205 }
206
onPrePrepare(GrRecordingContext * context,const GrSurfaceProxyView & writeView,GrAppliedClip * clip,const GrDstProxyView & dstProxyView,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)207 void StrokeTessellateOp::onPrePrepare(GrRecordingContext* context,
208 const GrSurfaceProxyView& writeView, GrAppliedClip* clip,
209 const GrDstProxyView& dstProxyView,
210 GrXferBarrierFlags renderPassXferBarriers, GrLoadOp
211 colorLoadOp) {
212 // DMSAA is not supported on DDL.
213 bool usesMSAASurface = writeView.asRenderTargetProxy()->numSamples() > 1;
214 this->prePrepareTessellator({context->priv().recordTimeAllocator(), writeView, usesMSAASurface,
215 &dstProxyView, renderPassXferBarriers, colorLoadOp,
216 context->priv().caps()},
217 (clip) ? std::move(*clip) : GrAppliedClip::Disabled());
218 if (fStencilProgram) {
219 context->priv().recordProgramInfo(fStencilProgram);
220 }
221 if (fFillProgram) {
222 context->priv().recordProgramInfo(fFillProgram);
223 }
224 }
225
onPrepare(GrOpFlushState * flushState)226 void StrokeTessellateOp::onPrepare(GrOpFlushState* flushState) {
227 if (!fTessellator) {
228 this->prePrepareTessellator({flushState->allocator(), flushState->writeView(),
229 flushState->usesMSAASurface(), &flushState->dstProxyView(),
230 flushState->renderPassBarriers(), flushState->colorLoadOp(),
231 &flushState->caps()}, flushState->detachAppliedClip());
232 }
233 SkASSERT(fTessellator);
234 fTessellator->prepare(flushState,
235 fViewMatrix,
236 &fPathStrokeList,
237 fTotalCombinedVerbCnt);
238 }
239
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)240 void StrokeTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
241 if (fStencilProgram) {
242 flushState->bindPipelineAndScissorClip(*fStencilProgram, chainBounds);
243 flushState->bindTextures(fStencilProgram->geomProc(), nullptr, fStencilProgram->pipeline());
244 fTessellator->draw(flushState);
245 }
246 if (fFillProgram) {
247 flushState->bindPipelineAndScissorClip(*fFillProgram, chainBounds);
248 flushState->bindTextures(fFillProgram->geomProc(), nullptr, fFillProgram->pipeline());
249 fTessellator->draw(flushState);
250 }
251 }
252
253 } // namespace skgpu::ganesh
254