| /* |
| * Copyright 2021 Google LLC. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/gpu/tessellate/PathCurveTessellator.h" |
| |
| #include "src/gpu/tessellate/AffineMatrix.h" |
| #include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h" |
| #include "src/gpu/tessellate/PatchWriter.h" |
| #include "src/gpu/tessellate/WangsFormula.h" |
| |
| #if SK_GPU_V1 |
| #include "src/gpu/GrMeshDrawTarget.h" |
| #include "src/gpu/GrOpFlushState.h" |
| #include "src/gpu/GrResourceProvider.h" |
| #endif |
| |
| namespace skgpu { |
| |
| using CubicPatch = PatchWriter::CubicPatch; |
| using ConicPatch = PatchWriter::ConicPatch; |
| using TrianglePatch = PatchWriter::TrianglePatch; |
| |
| int PathCurveTessellator::patchPreallocCount(int totalCombinedPathVerbCnt) const { |
| // Over-allocate enough curves for 1 in 4 to chop. |
| int approxNumChops = (totalCombinedPathVerbCnt + 3) / 4; |
| // Every chop introduces 2 new patches: another curve patch and a triangle patch that glues the |
| // two chops together. |
| return totalCombinedPathVerbCnt + approxNumChops * 2; |
| } |
| |
| void PathCurveTessellator::writePatches(PatchWriter& patchWriter, |
| int maxTessellationSegments, |
| const SkMatrix& shaderMatrix, |
| const PathDrawList& pathDrawList) { |
| float maxSegments_pow2 = pow2(maxTessellationSegments); |
| float maxSegments_pow4 = pow2(maxSegments_pow2); |
| |
| // If using fixed count, this is the number of segments we need to emit per instance. Always |
| // emit at least 2 segments so we can support triangles. |
| float numFixedSegments_pow4 = 2*2*2*2; |
| |
| for (auto [pathMatrix, path, color] : pathDrawList) { |
| AffineMatrix m(pathMatrix); |
| wangs_formula::VectorXform totalXform(SkMatrix::Concat(shaderMatrix, pathMatrix)); |
| if (fAttribs & PatchAttribs::kColor) { |
| patchWriter.updateColorAttrib(color); |
| } |
| for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) { |
| switch (verb) { |
| case SkPathVerb::kQuad: { |
| auto [p0, p1] = m.map2Points(pts); |
| auto p2 = m.map1Point(pts+2); |
| float n4 = wangs_formula::quadratic_pow4(kTessellationPrecision, |
| pts, |
| totalXform); |
| if (n4 <= 1) { |
| break; // This quad only needs 1 segment, which is empty. |
| } |
| if (n4 <= maxSegments_pow4) { |
| // This quad already fits in "maxTessellationSegments". |
| CubicPatch(patchWriter) << QuadToCubic{p0, p1, p2}; |
| } else { |
| // The path should have been pre-chopped if needed, so all curves fit in |
| // kMaxTessellationSegmentsPerCurve. |
| n4 = std::min(n4, pow4(kMaxTessellationSegmentsPerCurve)); |
| // Chop until each quad tessellation requires "maxSegments" or fewer. |
| int numPatches = |
| SkScalarCeilToInt(wangs_formula::root4(n4/maxSegments_pow4)); |
| patchWriter.chopAndWriteQuads(p0, p1, p2, numPatches); |
| } |
| numFixedSegments_pow4 = std::max(n4, numFixedSegments_pow4); |
| break; |
| } |
| |
| case SkPathVerb::kConic: { |
| auto [p0, p1] = m.map2Points(pts); |
| auto p2 = m.map1Point(pts+2); |
| float n2 = wangs_formula::conic_pow2(kTessellationPrecision, |
| pts, |
| *w, |
| totalXform); |
| if (n2 <= 1) { |
| break; // This conic only needs 1 segment, which is empty. |
| } |
| if (n2 <= maxSegments_pow2) { |
| // This conic already fits in "maxTessellationSegments". |
| ConicPatch(patchWriter) << p0 << p1 << p2 << *w; |
| } else { |
| // The path should have been pre-chopped if needed, so all curves fit in |
| // kMaxTessellationSegmentsPerCurve. |
| n2 = std::min(n2, pow2(kMaxTessellationSegmentsPerCurve)); |
| // Chop until each conic tessellation requires "maxSegments" or fewer. |
| int numPatches = SkScalarCeilToInt(sqrtf(n2/maxSegments_pow2)); |
| patchWriter.chopAndWriteConics(p0, p1, p2, *w, numPatches); |
| } |
| numFixedSegments_pow4 = std::max(n2*n2, numFixedSegments_pow4); |
| break; |
| } |
| |
| case SkPathVerb::kCubic: { |
| auto [p0, p1] = m.map2Points(pts); |
| auto [p2, p3] = m.map2Points(pts+2); |
| float n4 = wangs_formula::cubic_pow4(kTessellationPrecision, |
| pts, |
| totalXform); |
| if (n4 <= 1) { |
| break; // This cubic only needs 1 segment, which is empty. |
| } |
| if (n4 <= maxSegments_pow4) { |
| // This cubic already fits in "maxTessellationSegments". |
| CubicPatch(patchWriter) << p0 << p1 << p2 << p3; |
| } else { |
| // The path should have been pre-chopped if needed, so all curves fit in |
| // kMaxTessellationSegmentsPerCurve. |
| n4 = std::min(n4, pow4(kMaxTessellationSegmentsPerCurve)); |
| // Chop until each cubic tessellation requires "maxSegments" or fewer. |
| int numPatches = |
| SkScalarCeilToInt(wangs_formula::root4(n4/maxSegments_pow4)); |
| patchWriter.chopAndWriteCubics(p0, p1, p2, p3, numPatches); |
| } |
| numFixedSegments_pow4 = std::max(n4, numFixedSegments_pow4); |
| break; |
| } |
| |
| default: break; |
| } |
| } |
| } |
| |
| // log16(n^4) == log2(n). |
| // We already chopped curves to make sure none needed a higher resolveLevel than |
| // kMaxFixedResolveLevel. |
| fFixedResolveLevel = SkTPin(wangs_formula::nextlog16(numFixedSegments_pow4), |
| fFixedResolveLevel, |
| int(kMaxFixedResolveLevel)); |
| } |
| |
| void PathCurveTessellator::WriteFixedVertexBuffer(VertexWriter vertexWriter, size_t bufferSize) { |
| SkASSERT(bufferSize >= sizeof(SkPoint) * 2); |
| SkASSERT(bufferSize % sizeof(SkPoint) == 0); |
| int vertexCount = bufferSize / sizeof(SkPoint); |
| SkASSERT(vertexCount > 3); |
| SkDEBUGCODE(VertexWriter end = vertexWriter.makeOffset(vertexCount * sizeof(SkPoint));) |
| |
| // Lay out the vertices in "middle-out" order: |
| // |
| // T= 0/1, 1/1, ; resolveLevel=0 |
| // 1/2, ; resolveLevel=1 (0/2 and 2/2 are already in resolveLevel 0) |
| // 1/4, 3/4, ; resolveLevel=2 (2/4 is already in resolveLevel 1) |
| // 1/8, 3/8, 5/8, 7/8, ; resolveLevel=3 (2/8 and 6/8 are already in resolveLevel 2) |
| // ... ; resolveLevel=... |
| // |
| // Resolve level 0 is just the beginning and ending vertices. |
| vertexWriter << (float)0/*resolveLevel*/ << (float)0/*idx*/; |
| vertexWriter << (float)0/*resolveLevel*/ << (float)1/*idx*/; |
| |
| // Resolve levels 1..kMaxResolveLevel. |
| int maxResolveLevel = SkPrevLog2(vertexCount - 1); |
| SkASSERT((1 << maxResolveLevel) + 1 == vertexCount); |
| for (int resolveLevel = 1; resolveLevel <= maxResolveLevel; ++resolveLevel) { |
| int numSegmentsInResolveLevel = 1 << resolveLevel; |
| // Write out the odd vertices in this resolveLevel. The even vertices were already written |
| // out in previous resolveLevels and will be indexed from there. |
| for (int i = 1; i < numSegmentsInResolveLevel; i += 2) { |
| vertexWriter << (float)resolveLevel << (float)i; |
| } |
| } |
| |
| SkASSERT(vertexWriter == end); |
| } |
| |
| void PathCurveTessellator::WriteFixedIndexBufferBaseIndex(VertexWriter vertexWriter, |
| size_t bufferSize, |
| uint16_t baseIndex) { |
| SkASSERT(bufferSize % (sizeof(uint16_t) * 3) == 0); |
| int triangleCount = bufferSize / (sizeof(uint16_t) * 3); |
| SkASSERT(triangleCount >= 1); |
| SkTArray<std::array<uint16_t, 3>> indexData(triangleCount); |
| |
| // Connect the vertices with a middle-out triangulation. Refer to InitFixedCountVertexBuffer() |
| // for the exact vertex ordering. |
| // |
| // Resolve level 1 is just a single triangle at T=[0, 1/2, 1]. |
| const auto* neighborInLastResolveLevel = &indexData.push_back({baseIndex, |
| (uint16_t)(baseIndex + 2), |
| (uint16_t)(baseIndex + 1)}); |
| |
| // Resolve levels 2..maxResolveLevel |
| int maxResolveLevel = SkPrevLog2(triangleCount + 1); |
| uint16_t nextIndex = baseIndex + 3; |
| SkASSERT(NumCurveTrianglesAtResolveLevel(maxResolveLevel) == triangleCount); |
| for (int resolveLevel = 2; resolveLevel <= maxResolveLevel; ++resolveLevel) { |
| SkDEBUGCODE(auto* firstTriangleInCurrentResolveLevel = indexData.end()); |
| int numOuterTrianglelsInResolveLevel = 1 << (resolveLevel - 1); |
| SkASSERT(numOuterTrianglelsInResolveLevel % 2 == 0); |
| int numTrianglePairsInResolveLevel = numOuterTrianglelsInResolveLevel >> 1; |
| for (int i = 0; i < numTrianglePairsInResolveLevel; ++i) { |
| // First triangle shares the left edge of "neighborInLastResolveLevel". |
| indexData.push_back({(*neighborInLastResolveLevel)[0], |
| nextIndex++, |
| (*neighborInLastResolveLevel)[1]}); |
| // Second triangle shares the right edge of "neighborInLastResolveLevel". |
| indexData.push_back({(*neighborInLastResolveLevel)[1], |
| nextIndex++, |
| (*neighborInLastResolveLevel)[2]}); |
| ++neighborInLastResolveLevel; |
| } |
| SkASSERT(neighborInLastResolveLevel == firstTriangleInCurrentResolveLevel); |
| } |
| SkASSERT(indexData.count() == triangleCount); |
| SkASSERT(nextIndex == baseIndex + triangleCount + 2); |
| |
| vertexWriter << VertexWriter::Array(indexData.data(), indexData.count()); |
| } |
| |
| #if SK_GPU_V1 |
| |
| GR_DECLARE_STATIC_UNIQUE_KEY(gFixedVertexBufferKey); |
| GR_DECLARE_STATIC_UNIQUE_KEY(gFixedIndexBufferKey); |
| |
| void PathCurveTessellator::prepareFixedCountBuffers(GrMeshDrawTarget* target) { |
| GrResourceProvider* rp = target->resourceProvider(); |
| |
| GR_DEFINE_STATIC_UNIQUE_KEY(gFixedVertexBufferKey); |
| |
| fFixedVertexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kVertex, |
| FixedVertexBufferSize(kMaxFixedResolveLevel), |
| gFixedVertexBufferKey, |
| WriteFixedVertexBuffer); |
| |
| GR_DEFINE_STATIC_UNIQUE_KEY(gFixedIndexBufferKey); |
| |
| fFixedIndexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kIndex, |
| FixedIndexBufferSize(kMaxFixedResolveLevel), |
| gFixedIndexBufferKey, |
| WriteFixedIndexBuffer); |
| } |
| |
| void PathCurveTessellator::drawTessellated(GrOpFlushState* flushState) const { |
| for (const GrVertexChunk& chunk : fVertexChunkArray) { |
| flushState->bindBuffers(nullptr, nullptr, chunk.fBuffer); |
| flushState->draw(chunk.fCount * 4, chunk.fBase * 4); |
| } |
| } |
| |
| void PathCurveTessellator::drawFixedCount(GrOpFlushState* flushState) const { |
| if (!fFixedVertexBuffer || !fFixedIndexBuffer) { |
| return; |
| } |
| int fixedIndexCount = NumCurveTrianglesAtResolveLevel(fFixedResolveLevel) * 3; |
| for (const GrVertexChunk& chunk : fVertexChunkArray) { |
| flushState->bindBuffers(fFixedIndexBuffer, chunk.fBuffer, fFixedVertexBuffer); |
| flushState->drawIndexedInstanced(fixedIndexCount, 0, chunk.fCount, chunk.fBase, 0); |
| } |
| } |
| |
| void PathCurveTessellator::drawHullInstances(GrOpFlushState* flushState, |
| sk_sp<const GrGpuBuffer> vertexBufferIfNeeded) const { |
| for (const GrVertexChunk& chunk : fVertexChunkArray) { |
| flushState->bindBuffers(nullptr, chunk.fBuffer, vertexBufferIfNeeded); |
| flushState->drawInstanced(chunk.fCount, chunk.fBase, 4, 0); |
| } |
| } |
| |
| #endif |
| |
| } // namespace skgpu |