/src/skia/src/gpu/tessellate/GrPathWedgeTessellator.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2021 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/tessellate/GrPathWedgeTessellator.h" |
9 | | |
10 | | #include "src/gpu/GrMeshDrawTarget.h" |
11 | | #include "src/gpu/GrResourceProvider.h" |
12 | | #include "src/gpu/geometry/GrPathUtils.h" |
13 | | #include "src/gpu/geometry/GrWangsFormula.h" |
14 | | #include "src/gpu/tessellate/GrCullTest.h" |
15 | | #include "src/gpu/tessellate/GrPathXform.h" |
16 | | #include "src/gpu/tessellate/shaders/GrPathTessellationShader.h" |
17 | | |
18 | | namespace { |
19 | | |
20 | | constexpr static float kPrecision = GrTessellationShader::kLinearizationPrecision; |
21 | | |
22 | | // Parses out each contour in a path and tracks the midpoint. Example usage: |
23 | | // |
24 | | // SkTPathContourParser parser; |
25 | | // while (parser.parseNextContour()) { |
26 | | // SkPoint midpoint = parser.currentMidpoint(); |
27 | | // for (auto [verb, pts] : parser.currentContour()) { |
28 | | // ... |
29 | | // } |
30 | | // } |
31 | | // |
32 | | class MidpointContourParser { |
33 | | public: |
34 | | MidpointContourParser(const SkPath& path) |
35 | | : fPath(path) |
36 | | , fVerbs(SkPathPriv::VerbData(fPath)) |
37 | | , fNumRemainingVerbs(fPath.countVerbs()) |
38 | | , fPoints(SkPathPriv::PointData(fPath)) |
39 | 0 | , fWeights(SkPathPriv::ConicWeightData(fPath)) {} |
40 | | // Advances the internal state to the next contour in the path. Returns false if there are no |
41 | | // more contours. |
42 | 0 | bool parseNextContour() { |
43 | 0 | bool hasGeometry = false; |
44 | 0 | for (; fVerbsIdx < fNumRemainingVerbs; ++fVerbsIdx) { |
45 | 0 | switch (fVerbs[fVerbsIdx]) { |
46 | 0 | case SkPath::kMove_Verb: |
47 | 0 | if (!hasGeometry) { |
48 | 0 | fMidpoint = fPoints[fPtsIdx]; |
49 | 0 | fMidpointWeight = 1; |
50 | 0 | this->advance(); |
51 | 0 | ++fPtsIdx; |
52 | 0 | continue; |
53 | 0 | } |
54 | 0 | return true; |
55 | 0 | default: |
56 | 0 | continue; |
57 | 0 | case SkPath::kLine_Verb: |
58 | 0 | ++fPtsIdx; |
59 | 0 | break; |
60 | 0 | case SkPath::kConic_Verb: |
61 | 0 | ++fWtsIdx; |
62 | 0 | [[fallthrough]]; |
63 | 0 | case SkPath::kQuad_Verb: |
64 | 0 | fPtsIdx += 2; |
65 | 0 | break; |
66 | 0 | case SkPath::kCubic_Verb: |
67 | 0 | fPtsIdx += 3; |
68 | 0 | break; |
69 | 0 | } |
70 | 0 | fMidpoint += fPoints[fPtsIdx - 1]; |
71 | 0 | ++fMidpointWeight; |
72 | 0 | hasGeometry = true; |
73 | 0 | } |
74 | 0 | return hasGeometry; |
75 | 0 | } |
76 | | |
77 | | // Allows for iterating the current contour using a range-for loop. |
78 | 0 | SkPathPriv::Iterate currentContour() { |
79 | 0 | return SkPathPriv::Iterate(fVerbs, fVerbs + fVerbsIdx, fPoints, fWeights); |
80 | 0 | } |
81 | | |
82 | 0 | SkPoint currentMidpoint() { return fMidpoint * (1.f / fMidpointWeight); } |
83 | | |
84 | | private: |
85 | 0 | void advance() { |
86 | 0 | fVerbs += fVerbsIdx; |
87 | 0 | fNumRemainingVerbs -= fVerbsIdx; |
88 | 0 | fVerbsIdx = 0; |
89 | 0 | fPoints += fPtsIdx; |
90 | 0 | fPtsIdx = 0; |
91 | 0 | fWeights += fWtsIdx; |
92 | 0 | fWtsIdx = 0; |
93 | 0 | } |
94 | | |
95 | | const SkPath& fPath; |
96 | | |
97 | | const uint8_t* fVerbs; |
98 | | int fNumRemainingVerbs = 0; |
99 | | int fVerbsIdx = 0; |
100 | | |
101 | | const SkPoint* fPoints; |
102 | | int fPtsIdx = 0; |
103 | | |
104 | | const float* fWeights; |
105 | | int fWtsIdx = 0; |
106 | | |
107 | | SkPoint fMidpoint; |
108 | | int fMidpointWeight; |
109 | | }; |
110 | | |
111 | | // Writes out wedge patches, chopping as necessary so none require more segments than are supported |
112 | | // by the hardware. |
113 | | class WedgeWriter { |
114 | | public: |
115 | | WedgeWriter(GrMeshDrawTarget* target, |
116 | | GrVertexChunkArray* vertexChunkArray, |
117 | | size_t patchStride, |
118 | | int initialPatchAllocCount, |
119 | | int maxSegments) |
120 | | : fChunker(target, vertexChunkArray, patchStride, initialPatchAllocCount) |
121 | | , fMaxSegments_pow2(maxSegments * maxSegments) |
122 | 0 | , fMaxSegments_pow4(fMaxSegments_pow2 * fMaxSegments_pow2) { |
123 | 0 | } |
124 | | |
125 | | void setMatrices(const SkRect& cullBounds, |
126 | | const SkMatrix& shaderMatrix, |
127 | 0 | const SkMatrix& pathMatrix) { |
128 | 0 | SkMatrix totalMatrix; |
129 | 0 | totalMatrix.setConcat(shaderMatrix, pathMatrix); |
130 | 0 | fCullTest.set(cullBounds, totalMatrix); |
131 | 0 | fTotalVectorXform = totalMatrix; |
132 | 0 | fPathXform = pathMatrix; |
133 | 0 | } |
134 | | |
135 | 0 | const GrPathXform& pathXform() const { return fPathXform; } |
136 | | |
137 | | SK_ALWAYS_INLINE void writeFlatWedge(const GrShaderCaps& shaderCaps, |
138 | | SkPoint p0, |
139 | | SkPoint p1, |
140 | 0 | SkPoint midpoint) { |
141 | 0 | if (GrVertexWriter vertexWriter = fChunker.appendVertex()) { |
142 | 0 | fPathXform.mapLineToCubic(&vertexWriter, p0, p1); |
143 | 0 | vertexWriter.write(midpoint); |
144 | 0 | vertexWriter.write(GrVertexWriter::If(!shaderCaps.infinitySupport(), |
145 | 0 | GrTessellationShader::kCubicCurveType)); |
146 | 0 | } |
147 | 0 | } |
148 | | |
149 | | SK_ALWAYS_INLINE void writeQuadraticWedge(const GrShaderCaps& shaderCaps, |
150 | | const SkPoint p[3], |
151 | 0 | SkPoint midpoint) { |
152 | 0 | float numSegments_pow4 = GrWangsFormula::quadratic_pow4(kPrecision, p, fTotalVectorXform); |
153 | 0 | if (numSegments_pow4 > fMaxSegments_pow4) { |
154 | 0 | this->chopAndWriteQuadraticWedges(shaderCaps, p, midpoint); |
155 | 0 | return; |
156 | 0 | } |
157 | 0 | if (GrVertexWriter vertexWriter = fChunker.appendVertex()) { |
158 | 0 | fPathXform.mapQuadToCubic(&vertexWriter, p); |
159 | 0 | vertexWriter.write(midpoint); |
160 | 0 | vertexWriter.write(GrVertexWriter::If(!shaderCaps.infinitySupport(), |
161 | 0 | GrTessellationShader::kCubicCurveType)); |
162 | 0 | } |
163 | 0 | fNumFixedSegments_pow4 = std::max(numSegments_pow4, fNumFixedSegments_pow4); |
164 | 0 | } |
165 | | |
166 | | SK_ALWAYS_INLINE void writeConicWedge(const GrShaderCaps& shaderCaps, |
167 | | const SkPoint p[3], |
168 | | float w, |
169 | 0 | SkPoint midpoint) { |
170 | 0 | float numSegments_pow2 = GrWangsFormula::conic_pow2(kPrecision, p, w, fTotalVectorXform); |
171 | 0 | if (numSegments_pow2 > fMaxSegments_pow2) { |
172 | 0 | this->chopAndWriteConicWedges(shaderCaps, {p, w}, midpoint); |
173 | 0 | return; |
174 | 0 | } |
175 | 0 | if (GrVertexWriter vertexWriter = fChunker.appendVertex()) { |
176 | 0 | fPathXform.mapConicToPatch(&vertexWriter, p, w); |
177 | 0 | vertexWriter.write(midpoint); |
178 | 0 | vertexWriter.write(GrVertexWriter::If(!shaderCaps.infinitySupport(), |
179 | 0 | GrTessellationShader::kConicCurveType)); |
180 | 0 | } |
181 | 0 | fNumFixedSegments_pow4 = std::max(numSegments_pow2 * numSegments_pow2, |
182 | 0 | fNumFixedSegments_pow4); |
183 | 0 | } |
184 | | |
185 | | SK_ALWAYS_INLINE void writeCubicWedge(const GrShaderCaps& shaderCaps, |
186 | | const SkPoint p[4], |
187 | 0 | SkPoint midpoint) { |
188 | 0 | float numSegments_pow4 = GrWangsFormula::cubic_pow4(kPrecision, p, fTotalVectorXform); |
189 | 0 | if (numSegments_pow4 > fMaxSegments_pow4) { |
190 | 0 | this->chopAndWriteCubicWedges(shaderCaps, p, midpoint); |
191 | 0 | return; |
192 | 0 | } |
193 | 0 | if (GrVertexWriter vertexWriter = fChunker.appendVertex()) { |
194 | 0 | fPathXform.map4Points(&vertexWriter, p); |
195 | 0 | vertexWriter.write(midpoint); |
196 | 0 | vertexWriter.write(GrVertexWriter::If(!shaderCaps.infinitySupport(), |
197 | 0 | GrTessellationShader::kCubicCurveType)); |
198 | 0 | } |
199 | 0 | fNumFixedSegments_pow4 = std::max(numSegments_pow4, fNumFixedSegments_pow4); |
200 | 0 | } |
201 | | |
202 | 0 | int numFixedSegments_pow4() const { return fNumFixedSegments_pow4; } |
203 | | |
204 | | private: |
205 | | void chopAndWriteQuadraticWedges(const GrShaderCaps& shaderCaps, |
206 | | const SkPoint p[3], |
207 | 0 | SkPoint midpoint) { |
208 | 0 | SkPoint chops[5]; |
209 | 0 | SkChopQuadAtHalf(p, chops); |
210 | 0 | for (int i = 0; i < 2; ++i) { |
211 | 0 | const SkPoint* q = chops + i*2; |
212 | 0 | if (fCullTest.areVisible3(q)) { |
213 | 0 | this->writeQuadraticWedge(shaderCaps, q, midpoint); |
214 | 0 | } else { |
215 | 0 | this->writeFlatWedge(shaderCaps, q[0], q[2], midpoint); |
216 | 0 | } |
217 | 0 | } |
218 | 0 | } |
219 | | |
220 | | void chopAndWriteConicWedges(const GrShaderCaps& shaderCaps, |
221 | | const SkConic& conic, |
222 | 0 | SkPoint midpoint) { |
223 | 0 | SkConic chops[2]; |
224 | 0 | if (!conic.chopAt(.5, chops)) { |
225 | 0 | return; |
226 | 0 | } |
227 | 0 | for (int i = 0; i < 2; ++i) { |
228 | 0 | if (fCullTest.areVisible3(chops[i].fPts)) { |
229 | 0 | this->writeConicWedge(shaderCaps, chops[i].fPts, chops[i].fW, midpoint); |
230 | 0 | } else { |
231 | 0 | this->writeFlatWedge(shaderCaps, chops[i].fPts[0], chops[i].fPts[2], midpoint); |
232 | 0 | } |
233 | 0 | } |
234 | 0 | } |
235 | | |
236 | | void chopAndWriteCubicWedges(const GrShaderCaps& shaderCaps, |
237 | | const SkPoint p[4], |
238 | 0 | SkPoint midpoint) { |
239 | 0 | SkPoint chops[7]; |
240 | 0 | SkChopCubicAtHalf(p, chops); |
241 | 0 | for (int i = 0; i < 2; ++i) { |
242 | 0 | const SkPoint* c = chops + i*3; |
243 | 0 | if (fCullTest.areVisible4(c)) { |
244 | 0 | this->writeCubicWedge(shaderCaps, c, midpoint); |
245 | 0 | } else { |
246 | 0 | this->writeFlatWedge(shaderCaps, c[0], c[3], midpoint); |
247 | 0 | } |
248 | 0 | } |
249 | 0 | } |
250 | | |
251 | | GrVertexChunkBuilder fChunker; |
252 | | GrCullTest fCullTest; |
253 | | GrVectorXform fTotalVectorXform; |
254 | | GrPathXform fPathXform; |
255 | | const float fMaxSegments_pow2; |
256 | | const float fMaxSegments_pow4; |
257 | | |
258 | | // If using fixed count, this is the max number of curve segments we need to draw per instance. |
259 | | float fNumFixedSegments_pow4 = 1; |
260 | | }; |
261 | | |
262 | | } // namespace |
263 | | |
264 | | |
265 | | GrPathTessellator* GrPathWedgeTessellator::Make(SkArenaAlloc* arena, const SkMatrix& viewMatrix, |
266 | | const SkPMColor4f& color, int numPathVerbs, |
267 | 0 | const GrPipeline& pipeline, const GrCaps& caps) { |
268 | 0 | using PatchType = GrPathTessellationShader::PatchType; |
269 | 0 | GrPathTessellationShader* shader; |
270 | 0 | if (caps.shaderCaps()->tessellationSupport() && |
271 | 0 | caps.shaderCaps()->infinitySupport() && // The hw tessellation shaders use infinity. |
272 | 0 | !pipeline.usesLocalCoords() && // Our tessellation back door doesn't handle varyings. |
273 | 0 | numPathVerbs >= caps.minPathVerbsForHwTessellation()) { |
274 | 0 | shader = GrPathTessellationShader::MakeHardwareTessellationShader(arena, viewMatrix, color, |
275 | 0 | PatchType::kWedges); |
276 | 0 | } else { |
277 | 0 | shader = GrPathTessellationShader::MakeMiddleOutFixedCountShader(*caps.shaderCaps(), arena, |
278 | 0 | viewMatrix, color, |
279 | 0 | PatchType::kWedges); |
280 | 0 | } |
281 | 0 | return arena->make([=](void* objStart) { |
282 | 0 | return new(objStart) GrPathWedgeTessellator(shader); |
283 | 0 | }); |
284 | 0 | } |
285 | | |
286 | | GR_DECLARE_STATIC_UNIQUE_KEY(gFixedCountVertexBufferKey); |
287 | | GR_DECLARE_STATIC_UNIQUE_KEY(gFixedCountIndexBufferKey); |
288 | | |
289 | | void GrPathWedgeTessellator::prepare(GrMeshDrawTarget* target, |
290 | | const SkRect& cullBounds, |
291 | | const PathDrawList& pathDrawList, |
292 | 0 | int totalCombinedPathVerbCnt) { |
293 | 0 | SkASSERT(fVertexChunkArray.empty()); |
294 | |
|
295 | 0 | const GrShaderCaps& shaderCaps = *target->caps().shaderCaps(); |
296 | | |
297 | | // Over-allocate enough wedges for 1 in 4 to chop. |
298 | 0 | int maxWedges = MaxCombinedFanEdgesInPathDrawList(totalCombinedPathVerbCnt); |
299 | 0 | int wedgeAllocCount = (maxWedges * 5 + 3) / 4; // i.e., ceil(maxWedges * 5/4) |
300 | 0 | if (!wedgeAllocCount) { |
301 | 0 | return; |
302 | 0 | } |
303 | 0 | size_t patchStride = fShader->willUseTessellationShaders() ? fShader->vertexStride() * 5 |
304 | 0 | : fShader->instanceStride(); |
305 | |
|
306 | 0 | int maxSegments; |
307 | 0 | if (fShader->willUseTessellationShaders()) { |
308 | 0 | maxSegments = shaderCaps.maxTessellationSegments(); |
309 | 0 | } else { |
310 | 0 | maxSegments = GrPathTessellationShader::kMaxFixedCountSegments; |
311 | 0 | } |
312 | |
|
313 | 0 | WedgeWriter wedgeWriter(target, &fVertexChunkArray, patchStride, wedgeAllocCount, maxSegments); |
314 | 0 | for (auto [pathMatrix, path] : pathDrawList) { |
315 | 0 | wedgeWriter.setMatrices(cullBounds, fShader->viewMatrix(), pathMatrix); |
316 | 0 | MidpointContourParser parser(path); |
317 | 0 | while (parser.parseNextContour()) { |
318 | 0 | SkPoint midpoint = wedgeWriter.pathXform().mapPoint(parser.currentMidpoint()); |
319 | 0 | SkPoint startPoint = {0, 0}; |
320 | 0 | SkPoint lastPoint = startPoint; |
321 | 0 | for (auto [verb, pts, w] : parser.currentContour()) { |
322 | 0 | switch (verb) { |
323 | 0 | case SkPathVerb::kMove: |
324 | 0 | startPoint = lastPoint = pts[0]; |
325 | 0 | break; |
326 | 0 | case SkPathVerb::kClose: |
327 | 0 | break; // Ignore. We can assume an implicit close at the end. |
328 | 0 | case SkPathVerb::kLine: |
329 | 0 | wedgeWriter.writeFlatWedge(shaderCaps, pts[0], pts[1], midpoint); |
330 | 0 | lastPoint = pts[1]; |
331 | 0 | break; |
332 | 0 | case SkPathVerb::kQuad: |
333 | 0 | wedgeWriter.writeQuadraticWedge(shaderCaps, pts, midpoint); |
334 | 0 | lastPoint = pts[2]; |
335 | 0 | break; |
336 | 0 | case SkPathVerb::kConic: |
337 | 0 | wedgeWriter.writeConicWedge(shaderCaps, pts, *w, midpoint); |
338 | 0 | lastPoint = pts[2]; |
339 | 0 | break; |
340 | 0 | case SkPathVerb::kCubic: |
341 | 0 | wedgeWriter.writeCubicWedge(shaderCaps, pts, midpoint); |
342 | 0 | lastPoint = pts[3]; |
343 | 0 | break; |
344 | 0 | } |
345 | 0 | } |
346 | 0 | if (lastPoint != startPoint) { |
347 | 0 | wedgeWriter.writeFlatWedge(shaderCaps, lastPoint, startPoint, midpoint); |
348 | 0 | } |
349 | 0 | } |
350 | 0 | } |
351 | |
|
352 | 0 | if (!fShader->willUseTessellationShaders()) { |
353 | | // log2(n) == log16(n^4). |
354 | 0 | int fixedResolveLevel = GrWangsFormula::nextlog16(wedgeWriter.numFixedSegments_pow4()); |
355 | 0 | int numCurveTriangles = |
356 | 0 | GrPathTessellationShader::NumCurveTrianglesAtResolveLevel(fixedResolveLevel); |
357 | | // Emit 3 vertices per curve triangle, plus 3 more for the fan triangle. |
358 | 0 | fFixedIndexCount = numCurveTriangles * 3 + 3; |
359 | |
|
360 | 0 | GR_DEFINE_STATIC_UNIQUE_KEY(gFixedCountVertexBufferKey); |
361 | |
|
362 | 0 | fFixedCountVertexBuffer = target->resourceProvider()->findOrMakeStaticBuffer( |
363 | 0 | GrGpuBufferType::kVertex, |
364 | 0 | GrPathTessellationShader::SizeOfVertexBufferForMiddleOutWedges(), |
365 | 0 | gFixedCountVertexBufferKey, |
366 | 0 | GrPathTessellationShader::InitializeVertexBufferForMiddleOutWedges); |
367 | |
|
368 | 0 | GR_DEFINE_STATIC_UNIQUE_KEY(gFixedCountIndexBufferKey); |
369 | |
|
370 | 0 | fFixedCountIndexBuffer = target->resourceProvider()->findOrMakeStaticBuffer( |
371 | 0 | GrGpuBufferType::kIndex, |
372 | 0 | GrPathTessellationShader::SizeOfIndexBufferForMiddleOutWedges(), |
373 | 0 | gFixedCountIndexBufferKey, |
374 | 0 | GrPathTessellationShader::InitializeIndexBufferForMiddleOutWedges); |
375 | 0 | } |
376 | 0 | } Unexecuted instantiation: GrPathWedgeTessellator::prepare(GrMeshDrawTarget*, SkRect const&, GrPathTessellator::PathDrawList const&, int) Unexecuted instantiation: GrPathWedgeTessellator::prepare(GrMeshDrawTarget*, SkRect const&, GrPathTessellator::PathDrawList const&, int) |
377 | | |
378 | | #if SK_GPU_V1 |
379 | | #include "src/gpu/GrOpFlushState.h" |
380 | | |
381 | 0 | void GrPathWedgeTessellator::draw(GrOpFlushState* flushState) const { |
382 | 0 | if (fShader->willUseTessellationShaders()) { |
383 | 0 | for (const GrVertexChunk& chunk : fVertexChunkArray) { |
384 | 0 | flushState->bindBuffers(nullptr, nullptr, chunk.fBuffer); |
385 | 0 | flushState->draw(chunk.fCount * 5, chunk.fBase * 5); |
386 | 0 | } |
387 | 0 | } else { |
388 | 0 | SkASSERT(fShader->hasInstanceAttributes()); |
389 | 0 | for (const GrVertexChunk& chunk : fVertexChunkArray) { |
390 | 0 | flushState->bindBuffers(fFixedCountIndexBuffer, chunk.fBuffer, fFixedCountVertexBuffer); |
391 | 0 | flushState->drawIndexedInstanced(fFixedIndexCount, 0, chunk.fCount, chunk.fBase, 0); |
392 | 0 | } |
393 | 0 | } |
394 | 0 | } Unexecuted instantiation: GrPathWedgeTessellator::draw(GrOpFlushState*) const Unexecuted instantiation: GrPathWedgeTessellator::draw(GrOpFlushState*) const |
395 | | #endif |