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