/* * 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/geometry/GrPathUtils.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 { // 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 { // 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 { // 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> 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.writeArray(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 vertexBufferIfNeeded) const { for (const GrVertexChunk& chunk : fVertexChunkArray) { flushState->bindBuffers(nullptr, chunk.fBuffer, vertexBufferIfNeeded); flushState->drawInstanced(chunk.fCount, chunk.fBase, 4, 0); } } #endif } // namespace skgpu