xref: /aosp_15_r20/external/skia/src/gpu/ganesh/ops/StrokeTessellateOp.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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