/* * Copyright 2022 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/ganesh/tessellate/PathTessellator.h" #include "include/core/SkPathTypes.h" #include "include/private/base/SkAlignedStorage.h" #include "include/private/base/SkAssert.h" #include "include/private/base/SkDebug.h" #include "include/private/base/SkOnce.h" #include "include/private/base/SkPoint_impl.h" #include "include/private/gpu/ganesh/GrTypesPriv.h" #include "src/base/SkVx.h" #include "src/core/SkPathPriv.h" #include "src/gpu/ResourceKey.h" #include "src/gpu/ganesh/GrBuffer.h" #include "src/gpu/ganesh/GrMeshDrawTarget.h" #include "src/gpu/ganesh/GrOpFlushState.h" #include "src/gpu/ganesh/GrResourceProvider.h" #include "src/gpu/ganesh/tessellate/VertexChunkPatchAllocator.h" #include "src/gpu/tessellate/AffineMatrix.h" #include "src/gpu/tessellate/FixedCountBufferUtils.h" #include "src/gpu/tessellate/LinearTolerances.h" #include "src/gpu/tessellate/MidpointContourParser.h" #include "src/gpu/tessellate/PatchWriter.h" #include "src/gpu/tessellate/WangsFormula.h" #include namespace skgpu::ganesh { namespace { using namespace skgpu::tess; using CurveWriter = PatchWriter, Optional, Optional, AddTrianglesWhenChopping, DiscardFlatCurves>; void write_curve_patches(CurveWriter&& patchWriter, const SkMatrix& shaderMatrix, const PathTessellator::PathDrawList& pathDrawList) { patchWriter.setShaderTransform(wangs_formula::VectorXform{shaderMatrix}); for (auto [pathMatrix, path, color] : pathDrawList) { AffineMatrix m(pathMatrix); if (patchWriter.attribs() & 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); patchWriter.writeQuadratic(p0, p1, p2); break; } case SkPathVerb::kConic: { auto [p0, p1] = m.map2Points(pts); auto p2 = m.map1Point(pts+2); patchWriter.writeConic(p0, p1, p2, *w); break; } case SkPathVerb::kCubic: { auto [p0, p1] = m.map2Points(pts); auto [p2, p3] = m.map2Points(pts+2); patchWriter.writeCubic(p0, p1, p2, p3); break; } default: break; } } } } using WedgeWriter = PatchWriter, Optional, Optional, Optional>; void write_wedge_patches(WedgeWriter&& patchWriter, const SkMatrix& shaderMatrix, const PathTessellator::PathDrawList& pathDrawList) { patchWriter.setShaderTransform(wangs_formula::VectorXform{shaderMatrix}); for (auto [pathMatrix, path, color] : pathDrawList) { AffineMatrix m(pathMatrix); if (patchWriter.attribs() & PatchAttribs::kColor) { patchWriter.updateColorAttrib(color); } MidpointContourParser parser(path); while (parser.parseNextContour()) { patchWriter.updateFanPointAttrib(m.mapPoint(parser.currentMidpoint())); SkPoint lastPoint = {0, 0}; SkPoint startPoint = {0, 0}; for (auto [verb, pts, w] : parser.currentContour()) { switch (verb) { case SkPathVerb::kMove: { startPoint = lastPoint = pts[0]; break; } case SkPathVerb::kLine: { // Explicitly convert the line to an equivalent cubic w/ four distinct // control points because it fans better and avoids double-hitting pixels. patchWriter.writeLine(m.map2Points(pts)); lastPoint = pts[1]; break; } case SkPathVerb::kQuad: { auto [p0, p1] = m.map2Points(pts); auto p2 = m.map1Point(pts+2); patchWriter.writeQuadratic(p0, p1, p2); lastPoint = pts[2]; break; } case SkPathVerb::kConic: { auto [p0, p1] = m.map2Points(pts); auto p2 = m.map1Point(pts+2); patchWriter.writeConic(p0, p1, p2, *w); lastPoint = pts[2]; break; } case SkPathVerb::kCubic: { auto [p0, p1] = m.map2Points(pts); auto [p2, p3] = m.map2Points(pts+2); patchWriter.writeCubic(p0, p1, p2, p3); lastPoint = pts[3]; break; } case SkPathVerb::kClose: { break; // Ignore. We can assume an implicit close at the end. } } } if (lastPoint != startPoint) { SkPoint pts[2] = {lastPoint, startPoint}; patchWriter.writeLine(m.map2Points(pts)); } } } } } // namespace SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedCountCurveVertexBufferKey); SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedCountCurveIndexBufferKey); void PathCurveTessellator::prepareWithTriangles( GrMeshDrawTarget* target, const SkMatrix& shaderMatrix, GrInnerFanTriangulator::BreadcrumbTriangleList* extraTriangles, const PathDrawList& pathDrawList, int totalCombinedPathVerbCnt) { #if !defined(SK_ENABLE_OPTIMIZE_SIZE) int patchPreallocCount = FixedCountCurves::PreallocCount(totalCombinedPathVerbCnt) + (extraTriangles ? extraTriangles->count() : 0); #else SkASSERT(!extraTriangles); int patchPreallocCount = FixedCountCurves::PreallocCount(totalCombinedPathVerbCnt); #endif if (patchPreallocCount) { LinearTolerances worstCase; CurveWriter writer{fAttribs, &worstCase, target, &fVertexChunkArray, patchPreallocCount}; #if !defined(SK_ENABLE_OPTIMIZE_SIZE) // Write out extra space-filling triangles to connect the curve patches with any external // source of geometry (e.g. inner triangulation that handles winding explicitly). if (extraTriangles) { SkDEBUGCODE(int breadcrumbCount = 0;) for (const auto* tri = extraTriangles->head(); tri; tri = tri->fNext) { SkDEBUGCODE(++breadcrumbCount;) auto p0 = skvx::float2::Load(tri->fPts); auto p1 = skvx::float2::Load(tri->fPts + 1); auto p2 = skvx::float2::Load(tri->fPts + 2); if (any((p0 == p1) & (p1 == p2))) { // Cull completely horizontal or vertical triangles. GrTriangulator can't always // get these breadcrumb edges right when they run parallel to the sweep // direction because their winding is undefined by its current definition. // FIXME(skia:12060): This seemed safe, but if there is a view matrix it will // introduce T-junctions. continue; } writer.writeTriangle(p0, p1, p2); } SkASSERT(breadcrumbCount == extraTriangles->count()); } #endif write_curve_patches(std::move(writer), shaderMatrix, pathDrawList); fMaxVertexCount = FixedCountCurves::VertexCount(worstCase); } GrResourceProvider* rp = target->resourceProvider(); SKGPU_DEFINE_STATIC_UNIQUE_KEY(gFixedCountCurveVertexBufferKey); fFixedVertexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kVertex, FixedCountCurves::VertexBufferSize(), gFixedCountCurveVertexBufferKey, FixedCountCurves::WriteVertexBuffer); SKGPU_DEFINE_STATIC_UNIQUE_KEY(gFixedCountCurveIndexBufferKey); fFixedIndexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kIndex, FixedCountCurves::IndexBufferSize(), gFixedCountCurveIndexBufferKey, FixedCountCurves::WriteIndexBuffer); } void PathCurveTessellator::draw(GrOpFlushState* flushState) const { if (!fFixedVertexBuffer || !fFixedIndexBuffer) { return; } for (const GrVertexChunk& chunk : fVertexChunkArray) { flushState->bindBuffers(fFixedIndexBuffer, chunk.fBuffer, fFixedVertexBuffer); // The max vertex count is the logical number of vertices that the GPU needs to emit, so // since we're using drawIndexedInstanced, it's provided as the "index count" parameter. flushState->drawIndexedInstanced(fMaxVertexCount, 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); } } SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedCountWedgesVertexBufferKey); SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedCountWedgesIndexBufferKey); void PathWedgeTessellator::prepare(GrMeshDrawTarget* target, const SkMatrix& shaderMatrix, const PathDrawList& pathDrawList, int totalCombinedPathVerbCnt) { if (int patchPreallocCount = FixedCountWedges::PreallocCount(totalCombinedPathVerbCnt)) { LinearTolerances worstCase; WedgeWriter writer{fAttribs, &worstCase, target, &fVertexChunkArray, patchPreallocCount}; write_wedge_patches(std::move(writer), shaderMatrix, pathDrawList); fMaxVertexCount = FixedCountWedges::VertexCount(worstCase); } GrResourceProvider* rp = target->resourceProvider(); SKGPU_DEFINE_STATIC_UNIQUE_KEY(gFixedCountWedgesVertexBufferKey); fFixedVertexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kVertex, FixedCountWedges::VertexBufferSize(), gFixedCountWedgesVertexBufferKey, FixedCountWedges::WriteVertexBuffer); SKGPU_DEFINE_STATIC_UNIQUE_KEY(gFixedCountWedgesIndexBufferKey); fFixedIndexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kIndex, FixedCountWedges::IndexBufferSize(), gFixedCountWedgesIndexBufferKey, FixedCountWedges::WriteIndexBuffer); } void PathWedgeTessellator::draw(GrOpFlushState* flushState) const { if (!fFixedVertexBuffer || !fFixedIndexBuffer) { return; } for (const GrVertexChunk& chunk : fVertexChunkArray) { flushState->bindBuffers(fFixedIndexBuffer, chunk.fBuffer, fFixedVertexBuffer); flushState->drawIndexedInstanced(fMaxVertexCount, 0, chunk.fCount, chunk.fBase, 0); } } } // namespace skgpu::ganesh