| /* |
| * 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 "src/core/SkPathPriv.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/MiddleOutPolygonTriangulator.h" |
| #include "src/gpu/tessellate/MidpointContourParser.h" |
| #include "src/gpu/tessellate/PatchWriter.h" |
| #include "src/gpu/tessellate/WangsFormula.h" |
| |
| namespace skgpu::v1 { |
| |
| namespace { |
| |
| using namespace skgpu::tess; |
| |
| using CurveWriter = PatchWriter<VertexChunkPatchAllocator, |
| Optional<PatchAttribs::kColor>, |
| Optional<PatchAttribs::kWideColorIfEnabled>, |
| Optional<PatchAttribs::kExplicitCurveType>, |
| 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<VertexChunkPatchAllocator, |
| Required<PatchAttribs::kFanPoint>, |
| Optional<PatchAttribs::kColor>, |
| Optional<PatchAttribs::kWideColorIfEnabled>, |
| Optional<PatchAttribs::kExplicitCurveType>>; |
| |
| 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) { |
| int patchPreallocCount = FixedCountCurves::PreallocCount(totalCombinedPathVerbCnt) + |
| (extraTriangles ? extraTriangles->count() : 0); |
| if (patchPreallocCount) { |
| LinearTolerances worstCase; |
| CurveWriter writer{fAttribs, &worstCase, target, &fVertexChunkArray, patchPreallocCount}; |
| |
| // 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()); |
| } |
| |
| 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<const GrGpuBuffer> 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::v1 |