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