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