diff --git a/bench/TessellateBench.cpp b/bench/TessellateBench.cpp index f195e45e1b..c9bc83f744 100644 --- a/bench/TessellateBench.cpp +++ b/bench/TessellateBench.cpp @@ -11,17 +11,15 @@ #include "src/core/SkRectPriv.h" #include "src/gpu/GrDirectContextPriv.h" #include "src/gpu/mock/GrMockOpTarget.h" +#include "src/gpu/ops/PathTessellator.h" +#include "src/gpu/ops/StrokeTessellator.h" #include "src/gpu/tessellate/AffineMatrix.h" #include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h" -#include "src/gpu/tessellate/PathCurveTessellator.h" -#include "src/gpu/tessellate/PathWedgeTessellator.h" -#include "src/gpu/tessellate/StrokeFixedCountTessellator.h" -#include "src/gpu/tessellate/StrokeHardwareTessellator.h" #include "src/gpu/tessellate/WangsFormula.h" #include "tools/ToolUtils.h" #include -namespace skgpu { +namespace skgpu::v1 { // This is the number of cubics in desk_chalkboard.skp. (There are no quadratics in the chalkboard.) constexpr static int kNumCubicsInChalkboard = 47182; @@ -143,7 +141,7 @@ DEF_PATH_TESS_BENCH(GrPathCurveTessellator, make_cubic_path(8), SkMatrix::I()) { auto tess = PathCurveTessellator::Make(&arena, fTarget->caps().shaderCaps()->infinitySupport()); tess->prepare(fTarget.get(), - 1 << PathCurveTessellator::kMaxFixedResolveLevel, + kMaxParametricSegments, fMatrix, {gAlmostIdentity, fPath, SK_PMColor4fTRANSPARENT}, fPath.countVerbs(), @@ -157,7 +155,7 @@ DEF_PATH_TESS_BENCH(GrPathWedgeTessellator, make_cubic_path(8), SkMatrix::I()) { auto tess = PathWedgeTessellator::Make(&arena, fTarget->caps().shaderCaps()->infinitySupport()); tess->prepare(fTarget.get(), - 1 << PathCurveTessellator::kMaxFixedResolveLevel, + kMaxParametricSegments, fMatrix, {gAlmostIdentity, fPath, SK_PMColor4fTRANSPARENT}, fPath.countVerbs(), @@ -232,8 +230,7 @@ DEF_PATH_TESS_BENCH(middle_out_triangulation, ToolUtils::make_star(SkRect::MakeWH(500, 500), kNumCubicsInChalkboard), SkMatrix::I()) { // Conservative estimate of triangulation (see PathStencilCoverOp) - const int maxVerts = - 3 * (PathTessellator::MaxCombinedFanEdgesInPathDrawList(kNumCubicsInChalkboard) - 2); + const int maxVerts = 3 * (MaxCombinedFanEdgesInPaths(kNumCubicsInChalkboard) - 2); sk_sp buffer; int baseVertex; @@ -411,4 +408,4 @@ DEF_BENCH(return new TessPrepareBench( "GrStrokeFixedCountTessellator_motionmark"); ) -} // namespace skgpu +} // namespace skgpu::v1 diff --git a/experimental/graphite/src/render/MiddleOutFanRenderStep.cpp b/experimental/graphite/src/render/MiddleOutFanRenderStep.cpp index 6b592f0ef9..2c8328566d 100644 --- a/experimental/graphite/src/render/MiddleOutFanRenderStep.cpp +++ b/experimental/graphite/src/render/MiddleOutFanRenderStep.cpp @@ -12,8 +12,8 @@ #include "experimental/graphite/src/geom/Transform_graphite.h" #include "experimental/graphite/src/render/StencilAndCoverDSS.h" +#include "src/gpu/tessellate/FixedCountBufferUtils.h" #include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h" -#include "src/gpu/tessellate/PathTessellator.h" namespace skgpu { @@ -43,8 +43,7 @@ void MiddleOutFanRenderStep::writeVertices(DrawWriter* writer, // paths to SkPath just to iterate their pts/verbs SkPath path = shape.asPath(); - const int maxCombinedFanEdges = - PathTessellator::MaxCombinedFanEdgesInPathDrawList(path.countVerbs()); + const int maxCombinedFanEdges = MaxCombinedFanEdgesInPaths(path.countVerbs()); const int maxTrianglesInFans = std::max(maxCombinedFanEdges - 2, 0); DrawWriter::Vertices verts{*writer}; diff --git a/experimental/graphite/src/render/TessellateCurvesRenderStep.cpp b/experimental/graphite/src/render/TessellateCurvesRenderStep.cpp index 1a3063e9ed..c260186a97 100644 --- a/experimental/graphite/src/render/TessellateCurvesRenderStep.cpp +++ b/experimental/graphite/src/render/TessellateCurvesRenderStep.cpp @@ -13,9 +13,8 @@ #include "experimental/graphite/src/render/StencilAndCoverDSS.h" #include "src/gpu/tessellate/AffineMatrix.h" +#include "src/gpu/tessellate/FixedCountBufferUtils.h" #include "src/gpu/tessellate/PatchWriter.h" -#include "src/gpu/tessellate/PathCurveTessellator.h" -#include "src/gpu/tessellate/PathTessellator.h" namespace skgpu { @@ -42,8 +41,7 @@ struct DrawWriterAllocator { // TODO (skbug.com/13056): Actually compute optimal minimum required index count based on // PatchWriter's tracked segment count^4. static constexpr unsigned int kMaxIndexCount = - 3 * PathTessellator::NumCurveTrianglesAtResolveLevel( - PathTessellator::kMaxFixedResolveLevel); + 3 * NumCurveTrianglesAtResolveLevel(kMaxFixedResolveLevel); return fInstances.append(kMaxIndexCount, 1); } @@ -59,13 +57,6 @@ using Writer = PatchWriter; -size_t fixed_vertex_buffer_size() { - return PathCurveTessellator::FixedVertexBufferSize(PathTessellator::kMaxFixedResolveLevel); -} -size_t fixed_index_buffer_size() { - return PathCurveTessellator::FixedIndexBufferSize(PathTessellator::kMaxFixedResolveLevel); -} - } // namespace TessellateCurvesRenderStep::TessellateCurvesRenderStep(bool evenOdd) @@ -152,22 +143,20 @@ void TessellateCurvesRenderStep::writeVertices(DrawWriter* dw, const SkIRect& bounds, const Transform& localToDevice, const Shape& shape) const { - // TODO: Caps check - static constexpr int kMaxTessellationSegments = 1 << PathTessellator::kMaxFixedResolveLevel; SkPath path = shape.asPath(); // TODO: Iterate the Shape directly BindBufferInfo fixedVertexBuffer = dw->bufferManager()->getStaticBuffer( BufferType::kVertex, - PathCurveTessellator::WriteFixedVertexBuffer, - fixed_vertex_buffer_size); + FixedCountCurves::WriteVertexBuffer, + FixedCountCurves::VertexBufferSize); BindBufferInfo fixedIndexBuffer = dw->bufferManager()->getStaticBuffer( BufferType::kIndex, - PathCurveTessellator::WriteFixedIndexBuffer, - fixed_index_buffer_size); + FixedCountCurves::WriteIndexBuffer, + FixedCountCurves::IndexBufferSize); - int patchReserveCount = PathCurveTessellator::PatchPreallocCount(path.countVerbs()); - Writer writer{kAttribs, kMaxTessellationSegments, - *dw, fixedVertexBuffer, fixedIndexBuffer, patchReserveCount}; + int patchReserveCount = FixedCountCurves::PreallocCount(path.countVerbs()); + Writer writer{kAttribs, kMaxParametricSegments, + *dw, fixedVertexBuffer, fixedIndexBuffer, patchReserveCount}; // TODO: Is it better to pre-transform on the CPU and only have a matrix uniform to compute // local coords, or is it better to always transform on the GPU (less CPU usage, more diff --git a/experimental/graphite/src/render/TessellateWedgesRenderStep.cpp b/experimental/graphite/src/render/TessellateWedgesRenderStep.cpp index 31f43ff037..01dc83032d 100644 --- a/experimental/graphite/src/render/TessellateWedgesRenderStep.cpp +++ b/experimental/graphite/src/render/TessellateWedgesRenderStep.cpp @@ -12,10 +12,9 @@ #include "experimental/graphite/src/geom/Transform_graphite.h" #include "src/gpu/tessellate/AffineMatrix.h" +#include "src/gpu/tessellate/FixedCountBufferUtils.h" #include "src/gpu/tessellate/MidpointContourParser.h" #include "src/gpu/tessellate/PatchWriter.h" -#include "src/gpu/tessellate/PathTessellator.h" -#include "src/gpu/tessellate/PathWedgeTessellator.h" namespace skgpu { @@ -41,8 +40,7 @@ struct DrawWriterAllocator { // PatchWriter's tracked segment count^4. // Wedges use one extra triangle to connect to the fan point compared to the curve version. static constexpr unsigned int kMaxIndexCount = - 3 * (1 + PathTessellator::NumCurveTrianglesAtResolveLevel( - PathTessellator::kMaxFixedResolveLevel)); + 3 * (1 + NumCurveTrianglesAtResolveLevel(kMaxFixedResolveLevel)); return fInstances.append(kMaxIndexCount, 1); } @@ -57,13 +55,6 @@ static constexpr PatchAttribs kAttribs = PatchAttribs::kFanPoint; using Writer = PatchWriter>; -size_t fixed_vertex_buffer_size() { - return PathWedgeTessellator::FixedVertexBufferSize(PathTessellator::kMaxFixedResolveLevel); -} -size_t fixed_index_buffer_size() { - return PathWedgeTessellator::FixedIndexBufferSize(PathTessellator::kMaxFixedResolveLevel); -} - } // namespace TessellateWedgesRenderStep::TessellateWedgesRenderStep(std::string_view variantName, @@ -161,21 +152,19 @@ void TessellateWedgesRenderStep::writeVertices(DrawWriter* dw, const SkIRect& bounds, const Transform& localToDevice, const Shape& shape) const { - // TODO: Caps check - static constexpr int kMaxTessellationSegments = 1 << PathTessellator::kMaxFixedResolveLevel; SkPath path = shape.asPath(); // TODO: Iterate the Shape directly BindBufferInfo fixedVertexBuffer = dw->bufferManager()->getStaticBuffer( BufferType::kVertex, - PathWedgeTessellator::WriteFixedVertexBuffer, - fixed_vertex_buffer_size); + FixedCountWedges::WriteVertexBuffer, + FixedCountWedges::VertexBufferSize); BindBufferInfo fixedIndexBuffer = dw->bufferManager()->getStaticBuffer( BufferType::kIndex, - PathWedgeTessellator::WriteFixedIndexBuffer, - fixed_index_buffer_size); + FixedCountWedges::WriteIndexBuffer, + FixedCountWedges::IndexBufferSize); - int patchReserveCount = PathWedgeTessellator::PatchPreallocCount(path.countVerbs()); - Writer writer{kAttribs, kMaxTessellationSegments, + int patchReserveCount = FixedCountWedges::PreallocCount(path.countVerbs()); + Writer writer{kAttribs, kMaxParametricSegments, *dw, fixedVertexBuffer, fixedIndexBuffer, patchReserveCount}; // TODO: Is it better to pre-transform on the CPU and only have a matrix uniform to compute diff --git a/gn/gpu.gni b/gn/gpu.gni index 2d3dd221c6..ebe7e9f801 100644 --- a/gn/gpu.gni +++ b/gn/gpu.gni @@ -316,14 +316,6 @@ skia_gpu_sources = [ "$_src/gpu/gradients/GrGradientShader.cpp", "$_src/gpu/gradients/GrGradientShader.h", - # tessellate - "$_src/gpu/tessellate/StrokeFixedCountTessellator.cpp", - "$_src/gpu/tessellate/StrokeFixedCountTessellator.h", - "$_src/gpu/tessellate/StrokeHardwareTessellator.cpp", - "$_src/gpu/tessellate/StrokeHardwareTessellator.h", - "$_src/gpu/tessellate/StrokeIterator.h", - "$_src/gpu/tessellate/StrokeTessellator.h", - # tessellate/shaders "$_src/gpu/tessellate/shaders/GrPathTessellationShader.cpp", "$_src/gpu/tessellate/shaders/GrPathTessellationShader.h", @@ -530,6 +522,8 @@ skia_skgpu_v1_sources = [ "$_src/gpu/ops/PathStencilCoverOp.h", "$_src/gpu/ops/PathTessellateOp.cpp", "$_src/gpu/ops/PathTessellateOp.h", + "$_src/gpu/ops/PathTessellator.cpp", + "$_src/gpu/ops/PathTessellator.h", "$_src/gpu/ops/QuadPerEdgeAA.cpp", "$_src/gpu/ops/QuadPerEdgeAA.h", "$_src/gpu/ops/RegionOp.cpp", @@ -548,6 +542,8 @@ skia_skgpu_v1_sources = [ "$_src/gpu/ops/StrokeRectOp.h", "$_src/gpu/ops/StrokeTessellateOp.cpp", "$_src/gpu/ops/StrokeTessellateOp.h", + "$_src/gpu/ops/StrokeTessellator.cpp", + "$_src/gpu/ops/StrokeTessellator.h", "$_src/gpu/ops/TessellationPathRenderer.cpp", "$_src/gpu/ops/TessellationPathRenderer.h", "$_src/gpu/ops/TextureOp.cpp", @@ -819,14 +815,12 @@ skia_shared_gpu_sources = [ # tessellate "$_src/gpu/tessellate/AffineMatrix.h", "$_src/gpu/tessellate/CullTest.h", + "$_src/gpu/tessellate/FixedCountBufferUtils.cpp", + "$_src/gpu/tessellate/FixedCountBufferUtils.h", "$_src/gpu/tessellate/MiddleOutPolygonTriangulator.h", "$_src/gpu/tessellate/MidpointContourParser.h", "$_src/gpu/tessellate/PatchWriter.h", - "$_src/gpu/tessellate/PathCurveTessellator.cpp", - "$_src/gpu/tessellate/PathCurveTessellator.h", - "$_src/gpu/tessellate/PathTessellator.h", - "$_src/gpu/tessellate/PathWedgeTessellator.cpp", - "$_src/gpu/tessellate/PathWedgeTessellator.h", + "$_src/gpu/tessellate/StrokeIterator.h", "$_src/gpu/tessellate/Tessellation.cpp", "$_src/gpu/tessellate/Tessellation.h", "$_src/gpu/tessellate/WangsFormula.h", diff --git a/samplecode/SamplePathTessellators.cpp b/samplecode/SamplePathTessellators.cpp index a7bd6d577b..dd8763da33 100644 --- a/samplecode/SamplePathTessellators.cpp +++ b/samplecode/SamplePathTessellators.cpp @@ -16,15 +16,14 @@ #include "src/gpu/GrRecordingContextPriv.h" #include "src/gpu/ops/GrDrawOp.h" #include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" +#include "src/gpu/ops/PathTessellator.h" #include "src/gpu/ops/TessellationPathRenderer.h" #include "src/gpu/tessellate/AffineMatrix.h" #include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h" -#include "src/gpu/tessellate/PathCurveTessellator.h" -#include "src/gpu/tessellate/PathWedgeTessellator.h" #include "src/gpu/tessellate/shaders/GrPathTessellationShader.h" #include "src/gpu/v1/SurfaceDrawContext_v1.h" -namespace skgpu { +namespace skgpu::v1 { namespace { @@ -341,6 +340,6 @@ bool SamplePathTessellators::onChar(SkUnichar unichar) { Sample* MakeTessellatedPathSample() { return new SamplePathTessellators; } static SampleRegistry gTessellatedPathSample(MakeTessellatedPathSample); -} // namespace skgpu +} // namespace skgpu::v1 #endif // SK_SUPPORT_GPU diff --git a/src/gpu/ops/AtlasRenderTask.h b/src/gpu/ops/AtlasRenderTask.h index 533858fdc0..1c106a25c5 100644 --- a/src/gpu/ops/AtlasRenderTask.h +++ b/src/gpu/ops/AtlasRenderTask.h @@ -12,7 +12,7 @@ #include "src/core/SkTBlockList.h" #include "src/gpu/GrDynamicAtlas.h" #include "src/gpu/ops/OpsTask.h" -#include "src/gpu/tessellate/PathTessellator.h" +#include "src/gpu/ops/PathTessellator.h" struct SkIPoint16; diff --git a/src/gpu/ops/PathInnerTriangulateOp.cpp b/src/gpu/ops/PathInnerTriangulateOp.cpp index 18abd75a15..180333dba5 100644 --- a/src/gpu/ops/PathInnerTriangulateOp.cpp +++ b/src/gpu/ops/PathInnerTriangulateOp.cpp @@ -13,7 +13,7 @@ #include "src/gpu/GrRecordingContextPriv.h" #include "src/gpu/GrResourceProvider.h" #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" -#include "src/gpu/tessellate/PathCurveTessellator.h" +#include "src/gpu/ops/PathTessellator.h" #include "src/gpu/tessellate/shaders/GrPathTessellationShader.h" namespace skgpu::v1 { diff --git a/src/gpu/ops/PathInnerTriangulateOp.h b/src/gpu/ops/PathInnerTriangulateOp.h index 2949cd593a..b013315719 100644 --- a/src/gpu/ops/PathInnerTriangulateOp.h +++ b/src/gpu/ops/PathInnerTriangulateOp.h @@ -13,14 +13,10 @@ #include "src/gpu/ops/GrDrawOp.h" #include "src/gpu/tessellate/shaders/GrTessellationShader.h" -namespace skgpu { +namespace skgpu::v1 { class PathCurveTessellator; -}; - -namespace skgpu::v1 { - // This op is a 3-pass twist on the standard Redbook "stencil then cover" algorithm: // // 1) Tessellate the path's outer curves into the stencil buffer. diff --git a/src/gpu/ops/PathStencilCoverOp.cpp b/src/gpu/ops/PathStencilCoverOp.cpp index 489df99599..bf5b9b4d80 100644 --- a/src/gpu/ops/PathStencilCoverOp.cpp +++ b/src/gpu/ops/PathStencilCoverOp.cpp @@ -17,9 +17,8 @@ #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" #include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" #include "src/gpu/tessellate/AffineMatrix.h" +#include "src/gpu/tessellate/FixedCountBufferUtils.h" #include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h" -#include "src/gpu/tessellate/PathCurveTessellator.h" -#include "src/gpu/tessellate/PathWedgeTessellator.h" #include "src/gpu/tessellate/Tessellation.h" #include "src/gpu/tessellate/shaders/GrPathTessellationShader.h" @@ -238,8 +237,7 @@ void PathStencilCoverOp::onPrepare(GrOpFlushState* flushState) { // The inner fan isn't built into the tessellator. Generate a standard Redbook fan with a // middle-out topology. GrEagerDynamicVertexAllocator vertexAlloc(flushState, &fFanBuffer, &fFanBaseVertex); - int maxCombinedFanEdges = - PathTessellator::MaxCombinedFanEdgesInPathDrawList(fTotalCombinedPathVerbCnt); + int maxCombinedFanEdges = MaxCombinedFanEdgesInPaths(fTotalCombinedPathVerbCnt); // A single n-sided polygon is fanned by n-2 triangles. Multiple polygons with a combined // edge count of n are fanned by strictly fewer triangles. int maxTrianglesInFans = std::max(maxCombinedFanEdges - 2, 0); diff --git a/src/gpu/ops/PathStencilCoverOp.h b/src/gpu/ops/PathStencilCoverOp.h index a11efd7b79..89652817c7 100644 --- a/src/gpu/ops/PathStencilCoverOp.h +++ b/src/gpu/ops/PathStencilCoverOp.h @@ -10,7 +10,7 @@ #include "src/gpu/ops/FillPathFlags.h" #include "src/gpu/ops/GrDrawOp.h" -#include "src/gpu/tessellate/PathTessellator.h" +#include "src/gpu/ops/PathTessellator.h" #include "src/gpu/tessellate/shaders/GrTessellationShader.h" namespace skgpu::v1 { diff --git a/src/gpu/ops/PathTessellateOp.cpp b/src/gpu/ops/PathTessellateOp.cpp index 4c8f04c873..b56388b1ae 100644 --- a/src/gpu/ops/PathTessellateOp.cpp +++ b/src/gpu/ops/PathTessellateOp.cpp @@ -9,7 +9,6 @@ #include "src/gpu/GrAppliedClip.h" #include "src/gpu/GrOpFlushState.h" -#include "src/gpu/tessellate/PathWedgeTessellator.h" #include "src/gpu/tessellate/shaders/GrPathTessellationShader.h" namespace skgpu::v1 { diff --git a/src/gpu/ops/PathTessellateOp.h b/src/gpu/ops/PathTessellateOp.h index a310aadb73..2c0a4f09cd 100644 --- a/src/gpu/ops/PathTessellateOp.h +++ b/src/gpu/ops/PathTessellateOp.h @@ -9,7 +9,7 @@ #define PathTessellateOp_DEFINED #include "src/gpu/ops/GrDrawOp.h" -#include "src/gpu/tessellate/PathTessellator.h" +#include "src/gpu/ops/PathTessellator.h" #include "src/gpu/tessellate/Tessellation.h" #include "src/gpu/tessellate/shaders/GrTessellationShader.h" diff --git a/src/gpu/ops/PathTessellator.cpp b/src/gpu/ops/PathTessellator.cpp new file mode 100644 index 0000000000..8201980b69 --- /dev/null +++ b/src/gpu/ops/PathTessellator.cpp @@ -0,0 +1,303 @@ +/* + * 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/ops/PathTessellator.h" + +#include "src/core/SkPathPriv.h" +#include "src/gpu/GrMeshDrawTarget.h" +#include "src/gpu/GrOpFlushState.h" +#include "src/gpu/GrResourceProvider.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 CurveWriter = PatchWriter, + Optional, + Optional, + AddTrianglesWhenChopping, + DiscardFlatCurves>; + +int write_curve_patches(CurveWriter&& patchWriter, + const SkMatrix& shaderMatrix, + const PathTessellator::PathDrawList& pathDrawList) { + wangs_formula::VectorXform shaderXform(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, shaderXform); + break; + } + + case SkPathVerb::kConic: { + auto [p0, p1] = m.map2Points(pts); + auto p2 = m.map1Point(pts+2); + + patchWriter.writeConic(p0, p1, p2, *w, shaderXform); + break; + } + + case SkPathVerb::kCubic: { + auto [p0, p1] = m.map2Points(pts); + auto [p2, p3] = m.map2Points(pts+2); + + patchWriter.writeCubic(p0, p1, p2, p3, shaderXform); + break; + } + + default: break; + } + } + } + + return patchWriter.requiredResolveLevel(); +} + +using WedgeWriter = PatchWriter, + Optional, + Optional, + Optional>; + +int write_wedge_patches(WedgeWriter&& patchWriter, + const SkMatrix& shaderMatrix, + const PathTessellator::PathDrawList& pathDrawList) { + wangs_formula::VectorXform shaderXform(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, shaderXform); + 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, shaderXform); + 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, shaderXform); + 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)); + } + } + } + + return patchWriter.requiredResolveLevel(); +} + +} // namespace + +SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedCountCurveVertexBufferKey); +SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedCountCurveIndexBufferKey); + +void PathCurveTessellator::prepareWithTriangles( + GrMeshDrawTarget* target, + int maxTessellationSegments, + const SkMatrix& shaderMatrix, + GrInnerFanTriangulator::BreadcrumbTriangleList* extraTriangles, + const PathDrawList& pathDrawList, + int totalCombinedPathVerbCnt, + bool willUseTessellationShaders) { + int patchPreallocCount = FixedCountCurves::PreallocCount(totalCombinedPathVerbCnt) + + (extraTriangles ? extraTriangles->count() : 0); + if (patchPreallocCount) { + CurveWriter writer{fAttribs, maxTessellationSegments, + 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 = float2::Load(tri->fPts); + auto p1 = float2::Load(tri->fPts + 1); + auto p2 = float2::Load(tri->fPts + 2); + if (skvx::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()); + } + + int resolveLevel = write_curve_patches(std::move(writer), shaderMatrix, pathDrawList); + this->updateResolveLevel(resolveLevel); + } + if (!willUseTessellationShaders) { + this->prepareFixedCountBuffers(target); + } +} + +void PathCurveTessellator::prepareFixedCountBuffers(GrMeshDrawTarget* target) { + 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::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); + } +} + + +SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedCountWedgesVertexBufferKey); +SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedCountWedgesIndexBufferKey); + +void PathWedgeTessellator::prepareFixedCountBuffers(GrMeshDrawTarget* target) { + 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::prepare(GrMeshDrawTarget* target, + int maxTessellationSegments, + const SkMatrix& shaderMatrix, + const PathDrawList& pathDrawList, + int totalCombinedPathVerbCnt, + bool willUseTessellationShaders) { + if (int patchPreallocCount = FixedCountWedges::PreallocCount(totalCombinedPathVerbCnt)) { + WedgeWriter writer{fAttribs, maxTessellationSegments, + target, &fVertexChunkArray, patchPreallocCount}; + int resolveLevel = write_wedge_patches(std::move(writer), shaderMatrix, pathDrawList); + this->updateResolveLevel(resolveLevel); + } + if (!willUseTessellationShaders) { + this->prepareFixedCountBuffers(target); + } +} + +void PathWedgeTessellator::drawTessellated(GrOpFlushState* flushState) const { + for (const GrVertexChunk& chunk : fVertexChunkArray) { + flushState->bindBuffers(nullptr, nullptr, chunk.fBuffer); + flushState->draw(chunk.fCount * 5, chunk.fBase * 5); + } +} + +void PathWedgeTessellator::drawFixedCount(GrOpFlushState* flushState) const { + if (!fFixedVertexBuffer || !fFixedIndexBuffer) { + return; + } + // Emit 3 vertices per curve triangle, plus 3 more for the fan triangle. + int fixedIndexCount = (NumCurveTrianglesAtResolveLevel(fFixedResolveLevel) + 1) * 3; + for (const GrVertexChunk& chunk : fVertexChunkArray) { + flushState->bindBuffers(fFixedIndexBuffer, chunk.fBuffer, fFixedVertexBuffer); + flushState->drawIndexedInstanced(fixedIndexCount, 0, chunk.fCount, chunk.fBase, 0); + } +} + +} // namespace skgpu::v1 diff --git a/src/gpu/ops/PathTessellator.h b/src/gpu/ops/PathTessellator.h new file mode 100644 index 0000000000..6d2e96eb76 --- /dev/null +++ b/src/gpu/ops/PathTessellator.h @@ -0,0 +1,191 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef PathTessellator_DEFINED +#define PathTessellator_DEFINED + +#include "src/core/SkArenaAlloc.h" +#include "src/gpu/GrGpuBuffer.h" +#include "src/gpu/GrVertexChunkArray.h" +#include "src/gpu/geometry/GrInnerFanTriangulator.h" +#include "src/gpu/tessellate/FixedCountBufferUtils.h" +#include "src/gpu/tessellate/Tessellation.h" + +class GrMeshDrawTarget; +class GrOpFlushState; +class SkPath; + +namespace skgpu::v1 { + +// Prepares GPU data for, and then draws a path's tessellated geometry. Depending on the subclass, +// the caller may or may not be required to draw the path's inner fan separately. +class PathTessellator { +public: + + struct PathDrawList { + PathDrawList(const SkMatrix& pathMatrix, + const SkPath& path, + const SkPMColor4f& color, + PathDrawList* next = nullptr) + : fPathMatrix(pathMatrix), fPath(path), fColor(color), fNext(next) {} + + SkMatrix fPathMatrix; + SkPath fPath; + SkPMColor4f fColor; + PathDrawList* fNext; + + struct Iter { + void operator++() { fHead = fHead->fNext; } + bool operator!=(const Iter& b) const { return fHead != b.fHead; } + std::tuple operator*() const { + return {fHead->fPathMatrix, fHead->fPath, fHead->fColor}; + } + const PathDrawList* fHead; + }; + Iter begin() const { return {this}; } + Iter end() const { return {nullptr}; } + }; + + virtual ~PathTessellator() {} + + PatchAttribs patchAttribs() const { return fAttribs; } + + // Initializes the internal vertex and index buffers required for drawFixedCount(). + virtual void prepareFixedCountBuffers(GrMeshDrawTarget*) = 0; + + // Called before draw(). Prepares GPU buffers containing the geometry to tessellate. + virtual void prepare(GrMeshDrawTarget* target, + int maxTessellationSegments, + const SkMatrix& shaderMatrix, + const PathDrawList& pathDrawList, + int totalCombinedPathVerbCnt, + bool willUseTessellationShaders) = 0; + + // Issues hardware tessellation draw calls over the patches. The caller is responsible for + // binding its desired pipeline ahead of time. + virtual void drawTessellated(GrOpFlushState*) const = 0; + + // Issues fixed-count instanced draw calls over the patches. The caller is responsible for + // binding its desired pipeline ahead of time. + virtual void drawFixedCount(GrOpFlushState*) const = 0; + + void draw(GrOpFlushState* flushState, bool willUseTessellationShaders) { + if (willUseTessellationShaders) { + this->drawTessellated(flushState); + } else { + this->drawFixedCount(flushState); + } + } + +protected: + PathTessellator(bool infinitySupport, PatchAttribs attribs) : fAttribs(attribs) { + if (!infinitySupport) { + fAttribs |= PatchAttribs::kExplicitCurveType; + } + } + + void updateResolveLevel(int resolveLevel) { + // We should already chopped curves to make sure none needed a higher resolveLevel than + // kMaxFixedResolveLevel. + fFixedResolveLevel = SkTPin(resolveLevel, fFixedResolveLevel, skgpu::kMaxFixedResolveLevel); + } + + PatchAttribs fAttribs; + + // Calculated during prepare(). If using fixed count, this is the resolveLevel to use on our + // instanced draws. 2^resolveLevel == numSegments. + int fFixedResolveLevel = 0; + + GrVertexChunkArray fVertexChunkArray; + + // If using fixed-count rendering, these are the vertex and index buffers. + sk_sp fFixedVertexBuffer; + sk_sp fFixedIndexBuffer; +}; + +// Draws an array of "outer curve" patches. Each patch is an independent 4-point curve, representing +// either a cubic or a conic. Quadratics are converted to cubics and triangles are converted to +// conics with w=Inf. +class PathCurveTessellator final : public PathTessellator { +public: + static PathCurveTessellator* Make(SkArenaAlloc* arena, + bool infinitySupport, + PatchAttribs attribs = PatchAttribs::kNone) { + return arena->make(infinitySupport, attribs); + } + + PathCurveTessellator(bool infinitySupport, + PatchAttribs attribs = PatchAttribs::kNone) + : PathTessellator(infinitySupport, attribs) {} + + void prepareWithTriangles(GrMeshDrawTarget* target, + int maxTessellationSegments, + const SkMatrix& shaderMatrix, + GrInnerFanTriangulator::BreadcrumbTriangleList* extraTriangles, + const PathDrawList& pathDrawList, + int totalCombinedPathVerbCnt, + bool willUseTessellationShaders); + + void prepare(GrMeshDrawTarget* target, + int maxTessellationSegments, + const SkMatrix& shaderMatrix, + const PathDrawList& pathDrawList, + int totalCombinedPathVerbCnt, + bool willUseTessellationShaders) final { + this->prepareWithTriangles(target, + maxTessellationSegments, + shaderMatrix, + nullptr, // no extra triangles by default + pathDrawList, + totalCombinedPathVerbCnt, + willUseTessellationShaders); + } + + void prepareFixedCountBuffers(GrMeshDrawTarget*) final; + + void drawTessellated(GrOpFlushState*) const final; + void drawFixedCount(GrOpFlushState*) const final; + + // Draws a 4-point instance for each patch. This method is used for drawing convex hulls over + // each cubic with GrFillCubicHullShader. The caller is responsible for binding its desired + // pipeline ahead of time. + void drawHullInstances(GrOpFlushState*, sk_sp vertexBufferIfNeeded) const; +}; + +// Prepares an array of "wedge" patches. A wedge is an independent, 5-point closed contour +// consisting of 4 control points plus an anchor point fanning from the center of the curve's +// resident contour. A wedge can be either a cubic or a conic. Quadratics and lines are converted to +// cubics. Once stencilled, these wedges alone define the complete path. +class PathWedgeTessellator final : public PathTessellator { +public: + static PathWedgeTessellator* Make(SkArenaAlloc* arena, + bool infinitySupport, + PatchAttribs attribs = PatchAttribs::kNone) { + return arena->make(infinitySupport, attribs); + } + + PathWedgeTessellator(bool infinitySupport, PatchAttribs attribs = PatchAttribs::kNone) + : PathTessellator(infinitySupport, attribs) { + fAttribs |= PatchAttribs::kFanPoint; + } + + void prepare(GrMeshDrawTarget* target, + int maxTessellationSegments, + const SkMatrix& shaderMatrix, + const PathDrawList& pathDrawList, + int totalCombinedPathVerbCnt, + bool willUseTessellationShaders) final; + + void prepareFixedCountBuffers(GrMeshDrawTarget*) final; + + void drawTessellated(GrOpFlushState*) const final; + void drawFixedCount(GrOpFlushState*) const final; +}; + +} // namespace skgpu::v1 + +#endif // PathTessellator_DEFINED diff --git a/src/gpu/ops/StrokeTessellateOp.cpp b/src/gpu/ops/StrokeTessellateOp.cpp index 322fe09d8d..47836ea2ab 100644 --- a/src/gpu/ops/StrokeTessellateOp.cpp +++ b/src/gpu/ops/StrokeTessellateOp.cpp @@ -12,8 +12,6 @@ #include "src/gpu/GrAppliedClip.h" #include "src/gpu/GrOpFlushState.h" #include "src/gpu/GrRecordingContextPriv.h" -#include "src/gpu/tessellate/StrokeFixedCountTessellator.h" -#include "src/gpu/tessellate/StrokeHardwareTessellator.h" #include "src/gpu/tessellate/shaders/GrStrokeTessellationShader.h" namespace { diff --git a/src/gpu/ops/StrokeTessellateOp.h b/src/gpu/ops/StrokeTessellateOp.h index 8eecd24873..19fc688a58 100644 --- a/src/gpu/ops/StrokeTessellateOp.h +++ b/src/gpu/ops/StrokeTessellateOp.h @@ -10,7 +10,7 @@ #include "include/core/SkStrokeRec.h" #include "src/gpu/ops/GrDrawOp.h" -#include "src/gpu/tessellate/StrokeTessellator.h" +#include "src/gpu/ops/StrokeTessellator.h" #include "src/gpu/tessellate/shaders/GrTessellationShader.h" class GrRecordingContext; diff --git a/src/gpu/tessellate/StrokeHardwareTessellator.cpp b/src/gpu/ops/StrokeTessellator.cpp similarity index 76% rename from src/gpu/tessellate/StrokeHardwareTessellator.cpp rename to src/gpu/ops/StrokeTessellator.cpp index 9e810f3e9c..48dd4c9e10 100644 --- a/src/gpu/tessellate/StrokeHardwareTessellator.cpp +++ b/src/gpu/ops/StrokeTessellator.cpp @@ -1,26 +1,231 @@ /* - * Copyright 2020 Google LLC. + * 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/tessellate/StrokeHardwareTessellator.h" +#include "src/gpu/ops/StrokeTessellator.h" #include "src/core/SkGeometry.h" #include "src/core/SkPathPriv.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" -#endif +#include "src/gpu/GrResourceProvider.h" +#include "src/gpu/tessellate/PatchWriter.h" +#include "src/gpu/tessellate/StrokeIterator.h" +#include "src/gpu/tessellate/WangsFormula.h" -namespace skgpu { +namespace skgpu::v1 { namespace { +// Calculates and buffers up future values for "numRadialSegmentsPerRadian" using SIMD. +class alignas(sizeof(float) * 4) StrokeToleranceBuffer { +public: + using PathStrokeList = StrokeTessellator::PathStrokeList; + + StrokeToleranceBuffer(float matrixMaxScale) : fMatrixMaxScale(matrixMaxScale) {} + + float fetchRadialSegmentsPerRadian(PathStrokeList* head) { + // StrokeTessellateOp::onCombineIfPossible does not allow hairlines to become dynamic. If + // this changes, we will need to call StrokeTolerances::GetLocalStrokeWidth() for each + // stroke. + SkASSERT(!head->fStroke.isHairlineStyle()); + if (fBufferIdx == 4) { + // We ran out of values. Peek ahead and buffer up 4 more. + PathStrokeList* peekAhead = head; + int i = 0; + do { + fStrokeWidths[i++] = peekAhead->fStroke.getWidth(); + } while ((peekAhead = peekAhead->fNext) && i < 4); + auto tol = StrokeTolerances::ApproxNumRadialSegmentsPerRadian(fMatrixMaxScale, + fStrokeWidths); + tol.store(fNumRadialSegmentsPerRadian); + fBufferIdx = 0; + } + SkASSERT(0 <= fBufferIdx && fBufferIdx < 4); + SkASSERT(fStrokeWidths[fBufferIdx] == head->fStroke.getWidth()); + return fNumRadialSegmentsPerRadian[fBufferIdx++]; + } + +private: + float4 fStrokeWidths{}; // Must be first for alignment purposes. + float fNumRadialSegmentsPerRadian[4]; + const float fMatrixMaxScale; + int fBufferIdx = 4; // Initialize the buffer as "empty"; +}; + +// *** Fixed-count tessellation stroking + +using FixedCountStrokeWriter = PatchWriter, + Optional, + Optional, + Optional, + Optional, + TrackJoinControlPoints>; + +int write_fixed_count_patches(FixedCountStrokeWriter&& patchWriter, + const SkMatrix& shaderMatrix, + std::array matrixMinMaxScales, + StrokeTessellator::PathStrokeList* pathStrokeList) { + int maxEdgesInJoin = 0; + float maxRadialSegmentsPerRadian = 0; + const float matrixMaxScale = matrixMinMaxScales[1]; + if (!(patchWriter.attribs() & PatchAttribs::kStrokeParams)) { + // Strokes are static. Calculate tolerances once. + const SkStrokeRec& stroke = pathStrokeList->fStroke; + float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(matrixMinMaxScales.data(), + stroke.getWidth()); + float numRadialSegmentsPerRadian = StrokeTolerances::CalcNumRadialSegmentsPerRadian( + matrixMaxScale, localStrokeWidth); + maxEdgesInJoin = WorstCaseEdgesInJoin(stroke.getJoin(), numRadialSegmentsPerRadian); + maxRadialSegmentsPerRadian = numRadialSegmentsPerRadian; + } + + // Fast SIMD queue that buffers up values for "numRadialSegmentsPerRadian". Only used when we + // have dynamic stroke. + StrokeToleranceBuffer toleranceBuffer(matrixMaxScale); + + // The vector xform approximates how the control points are transformed by the shader to + // more accurately compute how many *parametric* segments are needed. + wangs_formula::VectorXform shaderXform{shaderMatrix}; + for (auto* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) { + const SkStrokeRec& stroke = pathStroke->fStroke; + if (patchWriter.attribs() & PatchAttribs::kStrokeParams) { + // Strokes are dynamic. Calculate tolerances every time. + float numRadialSegmentsPerRadian = + toleranceBuffer.fetchRadialSegmentsPerRadian(pathStroke); + maxEdgesInJoin = std::max( + WorstCaseEdgesInJoin(stroke.getJoin(), numRadialSegmentsPerRadian), + maxEdgesInJoin); + maxRadialSegmentsPerRadian = std::max(numRadialSegmentsPerRadian, + maxRadialSegmentsPerRadian); + patchWriter.updateStrokeParamsAttrib(stroke); + } + if (patchWriter.attribs() & PatchAttribs::kColor) { + patchWriter.updateColorAttrib(pathStroke->fColor); + } + + StrokeIterator strokeIter(pathStroke->fPath, &pathStroke->fStroke, &shaderMatrix); + while (strokeIter.next()) { + using Verb = StrokeIterator::Verb; + const SkPoint* p = strokeIter.pts(); + int numChops; + switch (strokeIter.verb()) { + case Verb::kContourFinished: + patchWriter.writeDeferredStrokePatch(); + break; + case Verb::kCircle: + // Round cap or else an empty stroke that is specified to be drawn as a circle. + patchWriter.writeCircle(p[0]); + [[fallthrough]]; + case Verb::kMoveWithinContour: + // A regular kMove invalidates the previous control point; the stroke iterator + // tells us a new value to use. + patchWriter.updateJoinControlPointAttrib(p[0]); + break; + case Verb::kLine: + patchWriter.writeLine(p[0], p[1]); + break; + case Verb::kQuad: + if (ConicHasCusp(p)) { + // The cusp is always at the midtandent. + SkPoint cusp = SkEvalQuadAt(p, SkFindQuadMidTangent(p)); + patchWriter.writeCircle(cusp); + // A quad can only have a cusp if it's flat with a 180-degree turnaround. + patchWriter.writeLine(p[0], cusp); + patchWriter.writeLine(cusp, p[2]); + } else { + patchWriter.writeQuadratic(p, shaderXform); + } + break; + case Verb::kConic: + if (ConicHasCusp(p)) { + // The cusp is always at the midtandent. + SkConic conic(p, strokeIter.w()); + SkPoint cusp = conic.evalAt(conic.findMidTangent()); + patchWriter.writeCircle(cusp); + // A conic can only have a cusp if it's flat with a 180-degree turnaround. + patchWriter.writeLine(p[0], cusp); + patchWriter.writeLine(cusp, p[2]); + } else { + patchWriter.writeConic(p, strokeIter.w(), shaderXform); + } + break; + case Verb::kCubic: + SkPoint chops[10]; + float T[2]; + bool areCusps; + numChops = FindCubicConvex180Chops(p, T, &areCusps); + if (numChops == 0) { + patchWriter.writeCubic(p, shaderXform); + } else if (numChops == 1) { + SkChopCubicAt(p, chops, T[0]); + if (areCusps) { + patchWriter.writeCircle(chops[3]); + // In a perfect world, these 3 points would be be equal after chopping + // on a cusp. + chops[2] = chops[4] = chops[3]; + } + patchWriter.writeCubic(chops, shaderXform); + patchWriter.writeCubic(chops + 3, shaderXform); + } else { + SkASSERT(numChops == 2); + SkChopCubicAt(p, chops, T[0], T[1]); + if (areCusps) { + patchWriter.writeCircle(chops[3]); + patchWriter.writeCircle(chops[6]); + // Two cusps are only possible if it's a flat line with two 180-degree + // turnarounds. + patchWriter.writeLine(chops[0], chops[3]); + patchWriter.writeLine(chops[3], chops[6]); + patchWriter.writeLine(chops[6], chops[9]); + } else { + patchWriter.writeCubic(chops, shaderXform); + patchWriter.writeCubic(chops + 3, shaderXform); + patchWriter.writeCubic(chops + 6, shaderXform); + } + } + break; + } + } + } + + // The maximum rotation we can have in a stroke is 180 degrees (SK_ScalarPI radians). + int maxRadialSegmentsInStroke = + std::max(SkScalarCeilToInt(maxRadialSegmentsPerRadian * SK_ScalarPI), 1); + + int maxParametricSegmentsInStroke = patchWriter.requiredFixedSegments(); + SkASSERT(maxParametricSegmentsInStroke >= 1); + + // Now calculate the maximum number of edges we will need in the stroke portion of the instance. + // The first and last edges in a stroke are shared by both the parametric and radial sets of + // edges, so the total number of edges is: + // + // numCombinedEdges = numParametricEdges + numRadialEdges - 2 + // + // It's also important to differentiate between the number of edges and segments in a strip: + // + // numSegments = numEdges - 1 + // + // So the total number of combined edges in the stroke is: + // + // numEdgesInStroke = numParametricSegments + 1 + numRadialSegments + 1 - 2 + // = numParametricSegments + numRadialSegments + // + int maxEdgesInStroke = maxRadialSegmentsInStroke + maxParametricSegmentsInStroke; + + // Each triangle strip has two sections: It starts with a join then transitions to a stroke. The + // number of edges in an instance is the sum of edges from the join and stroke sections both. + // NOTE: The final join edge and the first stroke edge are co-located, however we still need to + // emit both because the join's edge is half-width and the stroke's is full-width. + return maxEdgesInJoin + maxEdgesInStroke; +} + +// *** HW tessellation stroking *** + float num_combined_segments(float numParametricSegments, float numRadialSegments) { // The first and last edges are shared by both the parametric and radial sets of edges, so // the total number of edges is: @@ -55,7 +260,38 @@ void quad_to_cubic(const SkPoint p[3], SkPoint patch[4]) { patch[3] = p[2]; } -using PathStrokeList = StrokeTessellator::PathStrokeList; +SK_ALWAYS_INLINE bool cubic_has_cusp(const SkPoint p[4]) { + float2 p0 = skvx::bit_pun(p[0]); + float2 p1 = skvx::bit_pun(p[1]); + float2 p2 = skvx::bit_pun(p[2]); + float2 p3 = skvx::bit_pun(p[3]); + + // See FindCubicConvex180Chops() for the math. + float2 C = p1 - p0; + float2 D = p2 - p1; + float2 E = p3 - p0; + float2 B = D - C; + float2 A = -3*D + E; + + float a = cross(A, B); + float b = cross(A, C); + float c = cross(B, C); + float discr = b*b - 4*a*c; + + // If -cuspThreshold <= discr <= cuspThreshold, it means the two roots are within a distance of + // 2^-11 from one another in parametric space. This is close enough for our purposes to take the + // slow codepath that knows how to handle cusps. + constexpr static float kEpsilon = 1.f / (1 << 11); + float cuspThreshold = (2*kEpsilon) * a; + cuspThreshold *= cuspThreshold; + + return fabsf(discr) <= cuspThreshold && + // The most common type of cusp we encounter is when p0==p1 or p2==p3. Unless the curve + // is a flat line (a==b==c==0), these don't actually need special treatment because the + // cusp occurs at t=0 or t=1. + (!(skvx::all(p0 == p1) || skvx::all(p2 == p3)) || (a == 0 && b == 0 && c == 0)); +} + using HwPatchWriterBase = PatchWriter, Optional, @@ -654,49 +890,10 @@ private: SkPoint fJoinControlPoint; }; -SK_ALWAYS_INLINE bool cubic_has_cusp(const SkPoint p[4]) { - float2 p0 = skvx::bit_pun(p[0]); - float2 p1 = skvx::bit_pun(p[1]); - float2 p2 = skvx::bit_pun(p[2]); - float2 p3 = skvx::bit_pun(p[3]); - - // See FindCubicConvex180Chops() for the math. - float2 C = p1 - p0; - float2 D = p2 - p1; - float2 E = p3 - p0; - float2 B = D - C; - float2 A = -3*D + E; - - float a = cross(A, B); - float b = cross(A, C); - float c = cross(B, C); - float discr = b*b - 4*a*c; - - // If -cuspThreshold <= discr <= cuspThreshold, it means the two roots are within a distance of - // 2^-11 from one another in parametric space. This is close enough for our purposes to take the - // slow codepath that knows how to handle cusps. - constexpr static float kEpsilon = 1.f / (1 << 11); - float cuspThreshold = (2*kEpsilon) * a; - cuspThreshold *= cuspThreshold; - - return fabsf(discr) <= cuspThreshold && - // The most common type of cusp we encounter is when p0==p1 or p2==p3. Unless the curve - // is a flat line (a==b==c==0), these don't actually need special treatment because the - // cusp occurs at t=0 or t=1. - (!(skvx::all(p0 == p1) || skvx::all(p2 == p3)) || (a == 0 && b == 0 && c == 0)); -} - -int patch_prealloc_count(int totalCombinedStrokeVerbCnt) { - // Over-allocate enough patches for 1 in 4 strokes to chop and for 8 extra caps. - int strokePreallocCount = (totalCombinedStrokeVerbCnt * 5) / 4; - int capPreallocCount = 8; - return strokePreallocCount + capPreallocCount; -} - -void write_patches(HwPatchWriter&& hwPatchWriter, - const SkMatrix& shaderMatrix, - std::array matrixMinMaxScales, - StrokeTessellator::PathStrokeList* pathStrokeList) { +void write_hw_patches(HwPatchWriter&& hwPatchWriter, + const SkMatrix& shaderMatrix, + std::array matrixMinMaxScales, + StrokeTessellator::PathStrokeList* pathStrokeList) { using JoinType = HwPatchWriter::JoinType; float matrixMaxScale = matrixMinMaxScales[1]; @@ -873,17 +1070,76 @@ void write_patches(HwPatchWriter&& hwPatchWriter, } // namespace -#if SK_GPU_V1 + +SKGPU_DECLARE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey); + +int StrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target, + const SkMatrix& shaderMatrix, + std::array matrixMinMaxScales, + PathStrokeList* pathStrokeList, + int totalCombinedStrokeVerbCnt) { + int preallocCount = FixedCountStrokes::PreallocCount(totalCombinedStrokeVerbCnt); + FixedCountStrokeWriter patchWriter{fAttribs, kMaxParametricSegments, + target, &fVertexChunkArray, preallocCount}; + + fFixedEdgeCount = write_fixed_count_patches(std::move(patchWriter), + shaderMatrix, + matrixMinMaxScales, + pathStrokeList); + + // Don't draw more vertices than can be indexed by a signed short. We just have to draw the line + // somewhere and this seems reasonable enough. (There are two vertices per edge, so 2^14 edges + // make 2^15 vertices.) + fFixedEdgeCount = std::min(fFixedEdgeCount, (1 << 14) - 1); + + if (!target->caps().shaderCaps()->vertexIDSupport()) { + // Our shader won't be able to use sk_VertexID. Bind a fallback vertex buffer with the IDs + // in it instead. + constexpr static int kMaxEdgesInFallbackBuffer = 1024; + fFixedEdgeCount = std::min(fFixedEdgeCount, kMaxEdgesInFallbackBuffer); + + SKGPU_DEFINE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey); + + fVertexBufferIfNoIDSupport = target->resourceProvider()->findOrMakeStaticBuffer( + GrGpuBufferType::kVertex, + FixedCountStrokes::VertexBufferSize(kMaxEdgesInFallbackBuffer), + gVertexIDFallbackBufferKey, + FixedCountStrokes::WriteVertexBuffer); + } + + return fFixedEdgeCount; +} + +void StrokeFixedCountTessellator::draw(GrOpFlushState* flushState) const { + if (fVertexChunkArray.empty() || fFixedEdgeCount <= 0) { + return; + } + if (!flushState->caps().shaderCaps()->vertexIDSupport() && + !fVertexBufferIfNoIDSupport) { + return; + } + for (const auto& instanceChunk : fVertexChunkArray) { + flushState->bindBuffers(nullptr, instanceChunk.fBuffer, fVertexBufferIfNoIDSupport); + flushState->drawInstanced(instanceChunk.fCount, + instanceChunk.fBase, + fFixedEdgeCount * 2, + 0); + } +} int StrokeHardwareTessellator::prepare(GrMeshDrawTarget* target, const SkMatrix& shaderMatrix, std::array matrixMinMaxScales, PathStrokeList* pathStrokeList, int totalCombinedStrokeVerbCnt) { - int preallocCount = patch_prealloc_count(totalCombinedStrokeVerbCnt); + // Over-allocate enough patches for 1 in 4 strokes to chop and for 8 extra caps. + int strokePreallocCount = (totalCombinedStrokeVerbCnt * 5) / 4; + int capPreallocCount = 8; + int preallocCount = strokePreallocCount + capPreallocCount; + HwPatchWriter writer{target, &fVertexChunkArray, fAttribs, fMaxTessellationSegments, preallocCount, matrixMinMaxScales[1]}; - write_patches(std::move(writer), shaderMatrix, matrixMinMaxScales, pathStrokeList); + write_hw_patches(std::move(writer), shaderMatrix, matrixMinMaxScales, pathStrokeList); return 0; // 0 is returned for non-fixed-count stroke tessellation } @@ -894,6 +1150,4 @@ void StrokeHardwareTessellator::draw(GrOpFlushState* flushState) const { } } -#endif - -} // namespace skgpu +} // namespace skgpu::v1 diff --git a/src/gpu/ops/StrokeTessellator.h b/src/gpu/ops/StrokeTessellator.h new file mode 100644 index 0000000000..43fa042ffe --- /dev/null +++ b/src/gpu/ops/StrokeTessellator.h @@ -0,0 +1,107 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef StrokeTessellator_DEFINED +#define StrokeTessellator_DEFINED + +#include "include/core/SkPath.h" +#include "include/core/SkStrokeRec.h" +#include "include/private/SkColorData.h" +#include "src/core/SkMathPriv.h" +#include "src/gpu/GrVertexChunkArray.h" +#include "src/gpu/tessellate/FixedCountBufferUtils.h" +#include "src/gpu/tessellate/Tessellation.h" + +class GrGpuBuffer; +class GrMeshDrawTarget; +class GrOpFlushState; + +namespace skgpu::v1 { + +// Prepares GPU data for, and then draws a stroke's tessellated geometry. +class StrokeTessellator { +public: + struct PathStrokeList { + PathStrokeList(const SkPath& path, const SkStrokeRec& stroke, const SkPMColor4f& color) + : fPath(path), fStroke(stroke), fColor(color) {} + SkPath fPath; + SkStrokeRec fStroke; + SkPMColor4f fColor; + PathStrokeList* fNext = nullptr; + }; + + StrokeTessellator(PatchAttribs attribs) : fAttribs(attribs | PatchAttribs::kJoinControlPoint) {} + + // Called before draw(). Prepares GPU buffers containing the geometry to tessellate. + // + // Returns the fixed number of edges the tessellator will draw per patch, if using fixed-count + // rendering, otherwise 0. + virtual int prepare(GrMeshDrawTarget*, + const SkMatrix& shaderMatrix, + std::array matrixMinMaxScales, + PathStrokeList*, + int totalCombinedStrokeVerbCnt) = 0; + + // Issues draw calls for the tessellated stroke. The caller is responsible for creating and + // binding a pipeline that uses this class's shader() before calling draw(). + virtual void draw(GrOpFlushState*) const = 0; + + virtual ~StrokeTessellator() {} + +protected: + const PatchAttribs fAttribs; + + GrVertexChunkArray fVertexChunkArray; +}; + +// Renders strokes as fixed-count triangle strip instances. Any extra triangles not needed by the +// instance are emitted as degenerate triangles. +class StrokeFixedCountTessellator final : public StrokeTessellator { +public: + constexpr static int8_t kMaxParametricSegments_log2 = + SkNextLog2_portable(kMaxParametricSegments); + + StrokeFixedCountTessellator(PatchAttribs attribs) : StrokeTessellator(attribs) {} + + int prepare(GrMeshDrawTarget*, + const SkMatrix& shaderMatrix, + std::array matrixMinMaxScales, + PathStrokeList*, + int totalCombinedStrokeVerbCnt) final; + + void draw(GrOpFlushState*) const final; + +private: + int fFixedEdgeCount = 0; + + // Only used if sk_VertexID is not supported. + sk_sp fVertexBufferIfNoIDSupport; +}; + +// Renders opaque, constant-color strokes by decomposing them into standalone tessellation patches. +// Each patch is either a "cubic" (single stroked bezier curve with butt caps) or a "join". Requires +// MSAA if antialiasing is desired. +class StrokeHardwareTessellator final : public StrokeTessellator { +public: + StrokeHardwareTessellator(PatchAttribs attribs, int maxTessellationSegments) + : StrokeTessellator(attribs), fMaxTessellationSegments(maxTessellationSegments) {} + + int prepare(GrMeshDrawTarget*, + const SkMatrix& shaderMatrix, + std::array matrixMinMaxScales, + PathStrokeList*, + int totalCombinedStrokeVerbCnt) final; + + void draw(GrOpFlushState*) const final; + +private: + const int fMaxTessellationSegments; +}; + +} // namespace skgpu::v1 + +#endif // StrokeTessellator_DEFINED diff --git a/src/gpu/tessellate/FixedCountBufferUtils.cpp b/src/gpu/tessellate/FixedCountBufferUtils.cpp new file mode 100644 index 0000000000..7b28a2996a --- /dev/null +++ b/src/gpu/tessellate/FixedCountBufferUtils.cpp @@ -0,0 +1,134 @@ +/* + * 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/tessellate/FixedCountBufferUtils.h" + +#include "include/private/SkTArray.h" +#include "src/core/SkMathPriv.h" +#include "src/gpu/BufferWriter.h" + +#include + +namespace skgpu { + +namespace { + +void write_curve_index_buffer_base_index(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 << VertexWriter::Array(indexData.data(), indexData.count()); +} + +} // namespace + +void FixedCountCurves::WriteVertexBuffer(VertexWriter vertexWriter, size_t bufferSize) { + SkASSERT(bufferSize >= sizeof(SkPoint) * 2); + SkASSERT(bufferSize % sizeof(SkPoint) == 0); + int vertexCount = bufferSize / sizeof(SkPoint); + SkASSERT(vertexCount > 3); + SkDEBUGCODE(auto end = vertexWriter.mark(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.mark() == end); +} + +void FixedCountCurves::WriteIndexBuffer(VertexWriter vertexWriter, size_t bufferSize) { + write_curve_index_buffer_base_index(std::move(vertexWriter), bufferSize, /*baseIndex=*/0); +} + +void FixedCountWedges::WriteVertexBuffer(VertexWriter vertexWriter, size_t bufferSize) { + SkASSERT(bufferSize >= sizeof(SkPoint)); + + // Start out with the fan point. A negative resolve level indicates the fan point. + vertexWriter << -1.f/*resolveLevel*/ << -1.f/*idx*/; + + // The rest is the same as for curves. + FixedCountCurves::WriteVertexBuffer(std::move(vertexWriter), bufferSize - sizeof(SkPoint)); +} + +void FixedCountWedges::WriteIndexBuffer(VertexWriter vertexWriter, size_t bufferSize) { + SkASSERT(bufferSize >= sizeof(uint16_t) * 3); + + // Start out with the fan triangle. + vertexWriter << (uint16_t)0 << (uint16_t)1 << (uint16_t)2; + + // The rest is the same as for curves, with a baseIndex of 1. + write_curve_index_buffer_base_index(std::move(vertexWriter), + bufferSize - sizeof(uint16_t) * 3, + /*baseIndex=*/1); +} + +void FixedCountStrokes::WriteVertexBuffer(VertexWriter vertexWriter, size_t bufferSize) { + SkASSERT(bufferSize % (sizeof(float) * 2) == 0); + int edgeCount = bufferSize / (sizeof(float) * 2); + for (int i = 0; i < edgeCount; ++i) { + vertexWriter << (float)i << (float)-i; + } +} + +} // namespace skgpu diff --git a/src/gpu/tessellate/FixedCountBufferUtils.h b/src/gpu/tessellate/FixedCountBufferUtils.h new file mode 100644 index 0000000000..2cd4bebb50 --- /dev/null +++ b/src/gpu/tessellate/FixedCountBufferUtils.h @@ -0,0 +1,187 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef tessellate_FixedCountBufferUtils_DEFINED +#define tessellate_FixedCountBufferUtils_DEFINED + +#include "include/core/SkPaint.h" + +namespace skgpu { + +struct VertexWriter; + +// This is the maximum number of segments contained in our vertex and index buffers for +// fixed-count rendering. If rendering in fixed-count mode and a curve requires more segments, +// it must be chopped. +constexpr static int kMaxFixedResolveLevel = 5; + +// This is the maximum number of parametric segments (linear sections) that a curve can be split +// into. This is the same for path filling and stroking, although fixed-count stroking also uses +// additional vertices to handle radial segments, joins, and caps. Additionally the fixed-count +// path filling algorithms snap their dynamic vertex counts to powers-of-two, whereas the stroking +// algorithm does not. +constexpr static int kMaxParametricSegments = 1 << kMaxFixedResolveLevel; + +// Returns an upper bound on the number of combined edges there might be from all inner fans in +// a list of paths (specified by their total combined verb count). +constexpr static int MaxCombinedFanEdgesInPaths(int totalCombinedPathVerbCnt) { + // Path fans might have an extra edge from an implicit kClose at the end, but they also + // always begin with kMove. So the max possible number of edges in a single path is equal to + // the number of verbs. Therefore, the max number of combined fan edges in a path list is + // the number of combined verbs from the paths in the list. + return totalCombinedPathVerbCnt; +} + +// How many triangles are in a curve with 2^resolveLevel line segments? +// Resolve level defines the tessellation factor for filled paths drawn using curves or wedges. +constexpr static int NumCurveTrianglesAtResolveLevel(int resolveLevel) { + // resolveLevel=0 -> 0 line segments -> 0 triangles + // resolveLevel=1 -> 2 line segments -> 1 triangle + // resolveLevel=2 -> 4 line segments -> 3 triangles + // resolveLevel=3 -> 8 line segments -> 7 triangles + // ... + return (1 << resolveLevel) - 1; +} + +// Returns the fixed number of edges that are always emitted with the given join type. If the +// join is round, the caller needs to account for the additional radial edges on their own. +// Specifically, each join always emits: +// +// * Two colocated edges at the beginning (a full-width edge to seam with the preceding stroke +// and a half-width edge to begin the join). +// +// * An extra edge in the middle for miter joins, or else a variable number of radial edges +// for round joins (the caller is responsible for counting radial edges from round joins). +// +// * A half-width edge at the end of the join that will be colocated with the first +// (full-width) edge of the stroke. +// +constexpr static int NumFixedEdgesInJoin(SkPaint::Join joinType) { + switch (joinType) { + case SkPaint::kMiter_Join: + return 4; + case SkPaint::kRound_Join: + // The caller is responsible for counting the variable number of middle, radial + // segments on round joins. + [[fallthrough]]; + case SkPaint::kBevel_Join: + return 3; + } + SkUNREACHABLE; +} + +// Returns the worst-case number of edges we will need in order to draw a join of the given type. +constexpr static int WorstCaseEdgesInJoin(SkPaint::Join joinType, + float numRadialSegmentsPerRadian) { + int numEdges = NumFixedEdgesInJoin(joinType); + if (joinType == SkPaint::kRound_Join) { + // For round joins we need to count the radial edges on our own. Account for a worst-case + // join of 180 degrees (SK_ScalarPI radians). + numEdges += std::max(SkScalarCeilToInt(numRadialSegmentsPerRadian * SK_ScalarPI) - 1, 0); + } + return numEdges; +} + +/** + * Fixed-count tessellation operates in three modes, two for filling paths, and one for stroking. + * These modes may have additional sub-variations, but in terms of vertex buffer management, these + * three categories are sufficient: + * + * - FixedCountCurves: for filling paths where just the curves are tessellated. Additional measures + * to fill space between the inner control points of the paths are needed. + * - FixedCountWedges: for filling paths by tessellating the curves and adding an additional inline + * triangle with a shared vertex that all verbs connect to. Works with PatchAttribs::kFanPoint. + * - FixedCountStrokes: for stroking a path. Likely paired with PatchAttribs::kJoinControlPoint and + * PatchAttribs::kStrokeParams. + * + * The three types defined below for these three modes provide utility functions for heuristics to + * choose pre-allocation size when accumulating instance attributes with a PatchWriter, and + * functions for creating static/GPU-private vertex and index buffers that are used as the template + * for instanced rendering. + */ +class FixedCountCurves { + FixedCountCurves() = delete; +public: + // A heuristic function for reserving instance attribute space before using a PatchWriter. + static constexpr int PreallocCount(int totalCombinedPathVerbCnt) { + // Over-allocate enough curves for 1 in 4 to chop. Every chop introduces 2 new patches: + // another curve patch and a triangle patch that glues the two chops together, + // i.e. + 2 * ((count + 3) / 4) == (count + 3) / 2 + return totalCombinedPathVerbCnt + (totalCombinedPathVerbCnt + 3) / 2; + } + + // Return the number of bytes to allocate for a buffer filled via WriteVertexBuffer, assuming + // the shader and curve instances do require more than kMaxParametricSegments segments. + static constexpr size_t VertexBufferSize() { + return (kMaxParametricSegments + 1) * (2 * sizeof(float)); + } + + // As above but for the corresponding index buffer, written via WriteIndexBuffer. + static constexpr size_t IndexBufferSize() { + return NumCurveTrianglesAtResolveLevel(kMaxFixedResolveLevel) * 3 * sizeof(uint16_t); + } + + static void WriteVertexBuffer(VertexWriter, size_t bufferSize); + + static void WriteIndexBuffer(VertexWriter, size_t bufferSize); +}; + +class FixedCountWedges { + FixedCountWedges() = delete; +public: + // These functions provide equivalent functionality to the matching ones in FixedCountCurves, + // but are intended for use with a shader and PatchWriter that has enabled the kFanPoint attrib. + + static constexpr int PreallocCount(int totalCombinedPathVerbCnt) { + // Over-allocate enough wedges for 1 in 4 to chop, i.e., ceil(maxWedges * 5/4) + return (MaxCombinedFanEdgesInPaths(totalCombinedPathVerbCnt) * 5 + 3) / 4; + } + + static constexpr size_t VertexBufferSize() { + return ((kMaxParametricSegments + 1) + 1/*fan vertex*/) * (2 * sizeof(float)); + } + + static constexpr size_t IndexBufferSize() { + return (NumCurveTrianglesAtResolveLevel(kMaxFixedResolveLevel) + 1/*fan triangle*/) * + 3 * sizeof(uint16_t); + } + + static void WriteVertexBuffer(VertexWriter, size_t bufferSize); + + static void WriteIndexBuffer(VertexWriter, size_t bufferSize); +}; + +class FixedCountStrokes { + FixedCountStrokes() = delete; +public: + // These functions provide equivalent functionality to the matching ones in FixedCountCurves, + // but are intended for a shader that that strokes a path instead of filling, where vertices + // are associated with joins, caps, radial segments, or parametric segments. + // + // NOTE: The fixed-count stroke buffer is only needed when vertex IDs are not available as an + // SkSL built-in. And unlike the curve and wedge variants, stroke drawing never relies on an + // index buffer so those functions are not provided. + + static constexpr int PreallocCount(int totalCombinedPathVerbCnt) { + // Over-allocate enough patches for each stroke to chop once, and for 8 extra caps. Since + // we have to chop at inflections, points of 180 degree rotation, and anywhere a stroke + // requires too many parametric segments, many strokes will end up getting choppped. + return (totalCombinedPathVerbCnt * 2) + 8/* caps */; + } + + static constexpr size_t VertexBufferSize(int edgeCount) { + // Each vertex is a single float (explicit id) and each edge is composed of two vertices. + return 2 * edgeCount * sizeof(float); + } + + // Initializes the fallback vertex buffer that should be bound when sk_VertexID is not supported + static void WriteVertexBuffer(VertexWriter, size_t bufferSize); +}; + +} // namespace skgpu + +#endif // tessellate_FixedCountBufferUtils diff --git a/src/gpu/tessellate/PathCurveTessellator.cpp b/src/gpu/tessellate/PathCurveTessellator.cpp deleted file mode 100644 index 3204914958..0000000000 --- a/src/gpu/tessellate/PathCurveTessellator.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/* - * 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 { - -namespace { - -#if SK_GPU_V1 - -using Writer = PatchWriter, - Optional, - Optional, - AddTrianglesWhenChopping, - DiscardFlatCurves>; - -int write_patches(Writer&& patchWriter, - const SkMatrix& shaderMatrix, - const PathTessellator::PathDrawList& pathDrawList) { - wangs_formula::VectorXform shaderXform(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, shaderXform); - break; - } - - case SkPathVerb::kConic: { - auto [p0, p1] = m.map2Points(pts); - auto p2 = m.map1Point(pts+2); - - patchWriter.writeConic(p0, p1, p2, *w, shaderXform); - break; - } - - case SkPathVerb::kCubic: { - auto [p0, p1] = m.map2Points(pts); - auto [p2, p3] = m.map2Points(pts+2); - - patchWriter.writeCubic(p0, p1, p2, p3, shaderXform); - break; - } - - default: break; - } - } - } - - return patchWriter.requiredResolveLevel(); -} - -#endif - -} // namespace - -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(auto end = vertexWriter.mark(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.mark() == 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 << VertexWriter::Array(indexData.data(), indexData.count()); -} - -#if SK_GPU_V1 - -SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedVertexBufferKey); -SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedIndexBufferKey); - -void PathCurveTessellator::prepareWithTriangles( - GrMeshDrawTarget* target, - int maxTessellationSegments, - const SkMatrix& shaderMatrix, - GrInnerFanTriangulator::BreadcrumbTriangleList* extraTriangles, - const PathDrawList& pathDrawList, - int totalCombinedPathVerbCnt, - bool willUseTessellationShaders) { - int patchPreallocCount = PatchPreallocCount(totalCombinedPathVerbCnt) + - (extraTriangles ? extraTriangles->count() : 0); - if (patchPreallocCount) { - Writer writer{fAttribs, maxTessellationSegments, - 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 = float2::Load(tri->fPts); - auto p1 = float2::Load(tri->fPts + 1); - auto p2 = float2::Load(tri->fPts + 2); - if (skvx::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()); - } - - int resolveLevel = write_patches(std::move(writer), shaderMatrix, pathDrawList); - this->updateResolveLevel(resolveLevel); - } - if (!willUseTessellationShaders) { - this->prepareFixedCountBuffers(target); - } -} - -void PathCurveTessellator::prepareFixedCountBuffers(GrMeshDrawTarget* target) { - GrResourceProvider* rp = target->resourceProvider(); - - SKGPU_DEFINE_STATIC_UNIQUE_KEY(gFixedVertexBufferKey); - - fFixedVertexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kVertex, - FixedVertexBufferSize(kMaxFixedResolveLevel), - gFixedVertexBufferKey, - WriteFixedVertexBuffer); - - SKGPU_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 diff --git a/src/gpu/tessellate/PathCurveTessellator.h b/src/gpu/tessellate/PathCurveTessellator.h deleted file mode 100644 index 741bc63889..0000000000 --- a/src/gpu/tessellate/PathCurveTessellator.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2021 Google LLC. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#ifndef tessellate_PathCurveTessellator_DEFINED -#define tessellate_PathCurveTessellator_DEFINED - -#include "src/core/SkArenaAlloc.h" -#include "src/gpu/geometry/GrInnerFanTriangulator.h" -#include "src/gpu/tessellate/PathTessellator.h" - -namespace skgpu { - -// Draws an array of "outer curve" patches. Each patch is an independent 4-point curve, representing -// either a cubic or a conic. Quadratics are converted to cubics and triangles are converted to -// conics with w=Inf. -class PathCurveTessellator final : public PathTessellator { -public: - static PathCurveTessellator* Make(SkArenaAlloc* arena, - bool infinitySupport, - PatchAttribs attribs = PatchAttribs::kNone) { - return arena->make(infinitySupport, attribs); - } - - PathCurveTessellator(bool infinitySupport, - PatchAttribs attribs = PatchAttribs::kNone) - : PathTessellator(infinitySupport, attribs) {} - - static int PatchPreallocCount(int totalCombinedPathVerbCnt) { - // 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; - } - - // Size of the vertex buffer to use when rendering with a fixed count shader. - constexpr static int FixedVertexBufferSize(int maxFixedResolveLevel) { - return ((1 << maxFixedResolveLevel) + 1) * sizeof(SkPoint); - } - - // Writes the vertex buffer to use when rendering with a fixed count shader. - static void WriteFixedVertexBuffer(VertexWriter, size_t bufferSize); - - // Size of the index buffer to use when rendering with a fixed count shader. - constexpr static int FixedIndexBufferSize(int maxFixedResolveLevel) { - return NumCurveTrianglesAtResolveLevel(maxFixedResolveLevel) * 3 * sizeof(uint16_t); - } - - // Writes the index buffer to use when rendering with a fixed count shader. - static void WriteFixedIndexBuffer(VertexWriter vertexWriter, size_t bufferSize) { - WriteFixedIndexBufferBaseIndex(std::move(vertexWriter), bufferSize, 0); - } - - static void WriteFixedIndexBufferBaseIndex(VertexWriter, size_t bufferSize, uint16_t baseIndex); - -#if SK_GPU_V1 - void prepareWithTriangles(GrMeshDrawTarget* target, - int maxTessellationSegments, - const SkMatrix& shaderMatrix, - GrInnerFanTriangulator::BreadcrumbTriangleList* extraTriangles, - const PathDrawList& pathDrawList, - int totalCombinedPathVerbCnt, - bool willUseTessellationShaders); - - void prepare(GrMeshDrawTarget* target, - int maxTessellationSegments, - const SkMatrix& shaderMatrix, - const PathDrawList& pathDrawList, - int totalCombinedPathVerbCnt, - bool willUseTessellationShaders) final { - this->prepareWithTriangles(target, - maxTessellationSegments, - shaderMatrix, - nullptr, // no extra triangles by default - pathDrawList, - totalCombinedPathVerbCnt, - willUseTessellationShaders); - } - - void prepareFixedCountBuffers(GrMeshDrawTarget*) final; - - void drawTessellated(GrOpFlushState*) const final; - void drawFixedCount(GrOpFlushState*) const final; - - // Draws a 4-point instance for each patch. This method is used for drawing convex hulls over - // each cubic with GrFillCubicHullShader. The caller is responsible for binding its desired - // pipeline ahead of time. - void drawHullInstances(GrOpFlushState*, sk_sp vertexBufferIfNeeded) const; -#endif -}; - -} // namespace skgpu - -#endif // tessellate_PathCurveTessellator_DEFINED diff --git a/src/gpu/tessellate/PathTessellator.h b/src/gpu/tessellate/PathTessellator.h deleted file mode 100644 index 0c2c0dfaae..0000000000 --- a/src/gpu/tessellate/PathTessellator.h +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2021 Google LLC. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#ifndef tessellate_PathTessellator_DEFINED -#define tessellate_PathTessellator_DEFINED - -#include "include/core/SkPath.h" -#include "include/private/SkColorData.h" -#include "src/gpu/tessellate/Tessellation.h" - -class SkPath; - -#if SK_GPU_V1 -#include "src/gpu/GrGpuBuffer.h" -#include "src/gpu/GrVertexChunkArray.h" - -class GrMeshDrawTarget; -class GrOpFlushState; -#endif - -namespace skgpu { - -// Prepares GPU data for, and then draws a path's tessellated geometry. Depending on the subclass, -// the caller may or may not be required to draw the path's inner fan separately. -class PathTessellator { -public: - // This is the maximum number of segments contained in our vertex and index buffers for - // fixed-count rendering. If rendering in fixed-count mode and a curve requires more segments, - // it must be chopped. - constexpr static int kMaxFixedResolveLevel = 5; - - struct PathDrawList { - PathDrawList(const SkMatrix& pathMatrix, - const SkPath& path, - const SkPMColor4f& color, - PathDrawList* next = nullptr) - : fPathMatrix(pathMatrix), fPath(path), fColor(color), fNext(next) {} - - SkMatrix fPathMatrix; - SkPath fPath; - SkPMColor4f fColor; - PathDrawList* fNext; - - struct Iter { - void operator++() { fHead = fHead->fNext; } - bool operator!=(const Iter& b) const { return fHead != b.fHead; } - std::tuple operator*() const { - return {fHead->fPathMatrix, fHead->fPath, fHead->fColor}; - } - const PathDrawList* fHead; - }; - Iter begin() const { return {this}; } - Iter end() const { return {nullptr}; } - }; - - virtual ~PathTessellator() {} - - PatchAttribs patchAttribs() const { return fAttribs; } - -#if SK_GPU_V1 - // Initializes the internal vertex and index buffers required for drawFixedCount(). - virtual void prepareFixedCountBuffers(GrMeshDrawTarget*) = 0; - - // Called before draw(). Prepares GPU buffers containing the geometry to tessellate. - virtual void prepare(GrMeshDrawTarget* target, - int maxTessellationSegments, - const SkMatrix& shaderMatrix, - const PathDrawList& pathDrawList, - int totalCombinedPathVerbCnt, - bool willUseTessellationShaders) = 0; - - // Issues hardware tessellation draw calls over the patches. The caller is responsible for - // binding its desired pipeline ahead of time. - virtual void drawTessellated(GrOpFlushState*) const = 0; - - // Issues fixed-count instanced draw calls over the patches. The caller is responsible for - // binding its desired pipeline ahead of time. - virtual void drawFixedCount(GrOpFlushState*) const = 0; - - void draw(GrOpFlushState* flushState, bool willUseTessellationShaders) { - if (willUseTessellationShaders) { - this->drawTessellated(flushState); - } else { - this->drawFixedCount(flushState); - } - } -#endif - - // Returns an upper bound on the number of combined edges there might be from all inner fans in - // a PathDrawList. - static int MaxCombinedFanEdgesInPathDrawList(int totalCombinedPathVerbCnt) { - // Path fans might have an extra edge from an implicit kClose at the end, but they also - // always begin with kMove. So the max possible number of edges in a single path is equal to - // the number of verbs. Therefore, the max number of combined fan edges in a PathDrawList is - // the number of combined path verbs in that PathDrawList. - return totalCombinedPathVerbCnt; - } - - // How many triangles are in a curve with 2^resolveLevel line segments? - constexpr static int NumCurveTrianglesAtResolveLevel(int resolveLevel) { - // resolveLevel=0 -> 0 line segments -> 0 triangles - // resolveLevel=1 -> 2 line segments -> 1 triangle - // resolveLevel=2 -> 4 line segments -> 3 triangles - // resolveLevel=3 -> 8 line segments -> 7 triangles - // ... - return (1 << resolveLevel) - 1; - } - -protected: - PathTessellator(bool infinitySupport, PatchAttribs attribs) : fAttribs(attribs) { - if (!infinitySupport) { - fAttribs |= PatchAttribs::kExplicitCurveType; - } - } - - PatchAttribs fAttribs; - -#if SK_GPU_V1 - // Calculated during prepare(). If using fixed count, this is the resolveLevel to use on our - // instanced draws. 2^resolveLevel == numSegments. - int fFixedResolveLevel = 0; - - GrVertexChunkArray fVertexChunkArray; - - // If using fixed-count rendering, these are the vertex and index buffers. - sk_sp fFixedVertexBuffer; - sk_sp fFixedIndexBuffer; - - - void updateResolveLevel(int resolveLevel) { - // We should already chopped curves to make sure none needed a higher resolveLevel than - // kMaxFixedResolveLevel. - fFixedResolveLevel = SkTPin(resolveLevel, fFixedResolveLevel, kMaxFixedResolveLevel); - } -#endif -}; - -} // namespace skgpu - -#endif // tessellate_PathTessellator_DEFINED diff --git a/src/gpu/tessellate/PathWedgeTessellator.cpp b/src/gpu/tessellate/PathWedgeTessellator.cpp deleted file mode 100644 index 855e8e3243..0000000000 --- a/src/gpu/tessellate/PathWedgeTessellator.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* - * 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/PathWedgeTessellator.h" - -#include "src/core/SkPathPriv.h" -#include "src/gpu/tessellate/AffineMatrix.h" -#include "src/gpu/tessellate/MidpointContourParser.h" -#include "src/gpu/tessellate/PatchWriter.h" -#include "src/gpu/tessellate/PathCurveTessellator.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 { - -namespace { - -#if SK_GPU_V1 - -using Writer = PatchWriter, - Optional, - Optional, - Optional>; - -int write_patches(Writer&& patchWriter, - const SkMatrix& shaderMatrix, - const PathTessellator::PathDrawList& pathDrawList) { - wangs_formula::VectorXform shaderXform(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, shaderXform); - 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, shaderXform); - 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, shaderXform); - 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)); - } - } - } - - return patchWriter.requiredResolveLevel(); -} - -#endif - -} // namespace - -void PathWedgeTessellator::WriteFixedVertexBuffer(VertexWriter vertexWriter, size_t bufferSize) { - SkASSERT(bufferSize >= sizeof(SkPoint)); - - // Start out with the fan point. A negative resolve level indicates the fan point. - vertexWriter << -1.f/*resolveLevel*/ << -1.f/*idx*/; - - // The rest is the same as for curves. - PathCurveTessellator::WriteFixedVertexBuffer(std::move(vertexWriter), - bufferSize - sizeof(SkPoint)); -} - -void PathWedgeTessellator::WriteFixedIndexBuffer(VertexWriter vertexWriter, size_t bufferSize) { - SkASSERT(bufferSize >= sizeof(uint16_t) * 3); - - // Start out with the fan triangle. - vertexWriter << (uint16_t)0 << (uint16_t)1 << (uint16_t)2; - - // The rest is the same as for curves, with a baseIndex of 1. - PathCurveTessellator::WriteFixedIndexBufferBaseIndex(std::move(vertexWriter), - bufferSize - sizeof(uint16_t) * 3, - 1); -} - -#if SK_GPU_V1 - -SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedVertexBufferKey); -SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedIndexBufferKey); - -void PathWedgeTessellator::prepareFixedCountBuffers(GrMeshDrawTarget* target) { - GrResourceProvider* rp = target->resourceProvider(); - - SKGPU_DEFINE_STATIC_UNIQUE_KEY(gFixedVertexBufferKey); - - fFixedVertexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kVertex, - FixedVertexBufferSize(kMaxFixedResolveLevel), - gFixedVertexBufferKey, - WriteFixedVertexBuffer); - - SKGPU_DEFINE_STATIC_UNIQUE_KEY(gFixedIndexBufferKey); - - fFixedIndexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kIndex, - FixedIndexBufferSize(kMaxFixedResolveLevel), - gFixedIndexBufferKey, - WriteFixedIndexBuffer); -} - -void PathWedgeTessellator::prepare(GrMeshDrawTarget* target, - int maxTessellationSegments, - const SkMatrix& shaderMatrix, - const PathDrawList& pathDrawList, - int totalCombinedPathVerbCnt, - bool willUseTessellationShaders) { - if (int patchPreallocCount = PatchPreallocCount(totalCombinedPathVerbCnt)) { - Writer writer{fAttribs, maxTessellationSegments, - target, &fVertexChunkArray, patchPreallocCount}; - int resolveLevel = write_patches(std::move(writer), shaderMatrix, pathDrawList); - this->updateResolveLevel(resolveLevel); - } - if (!willUseTessellationShaders) { - this->prepareFixedCountBuffers(target); - } -} - -void PathWedgeTessellator::drawTessellated(GrOpFlushState* flushState) const { - for (const GrVertexChunk& chunk : fVertexChunkArray) { - flushState->bindBuffers(nullptr, nullptr, chunk.fBuffer); - flushState->draw(chunk.fCount * 5, chunk.fBase * 5); - } -} - -void PathWedgeTessellator::drawFixedCount(GrOpFlushState* flushState) const { - if (!fFixedVertexBuffer || !fFixedIndexBuffer) { - return; - } - // Emit 3 vertices per curve triangle, plus 3 more for the fan triangle. - int fixedIndexCount = (NumCurveTrianglesAtResolveLevel(fFixedResolveLevel) + 1) * 3; - for (const GrVertexChunk& chunk : fVertexChunkArray) { - flushState->bindBuffers(fFixedIndexBuffer, chunk.fBuffer, fFixedVertexBuffer); - flushState->drawIndexedInstanced(fixedIndexCount, 0, chunk.fCount, chunk.fBase, 0); - } -} - -#endif - -} // namespace skgpu diff --git a/src/gpu/tessellate/PathWedgeTessellator.h b/src/gpu/tessellate/PathWedgeTessellator.h deleted file mode 100644 index eb4767962f..0000000000 --- a/src/gpu/tessellate/PathWedgeTessellator.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2021 Google LLC. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#ifndef tessellate_PathWedgeTessellator_DEFINED -#define tessellate_PathWedgeTessellator_DEFINED - -#include "src/gpu/tessellate/PathTessellator.h" - -#include "src/core/SkArenaAlloc.h" - -namespace skgpu { - -// Prepares an array of "wedge" patches. A wedge is an independent, 5-point closed contour -// consisting of 4 control points plus an anchor point fanning from the center of the curve's -// resident contour. A wedge can be either a cubic or a conic. Quadratics and lines are converted to -// cubics. Once stencilled, these wedges alone define the complete path. -class PathWedgeTessellator final : public PathTessellator { -public: - static PathWedgeTessellator* Make(SkArenaAlloc* arena, - bool infinitySupport, - PatchAttribs attribs = PatchAttribs::kNone) { - return arena->make(infinitySupport, attribs); - } - - PathWedgeTessellator(bool infinitySupport, PatchAttribs attribs = PatchAttribs::kNone) - : PathTessellator(infinitySupport, attribs) { - fAttribs |= PatchAttribs::kFanPoint; - } - - static int PatchPreallocCount(int totalCombinedPathVerbCnt) { - // Over-allocate enough wedges for 1 in 4 to chop. - int maxWedges = MaxCombinedFanEdgesInPathDrawList(totalCombinedPathVerbCnt); - return (maxWedges * 5 + 3) / 4; // i.e., ceil(maxWedges * 5/4) - } - - // Size of the vertex buffer to use when rendering with a fixed count shader. - constexpr static int FixedVertexBufferSize(int maxFixedResolveLevel) { - return (((1 << maxFixedResolveLevel) + 1) + 1/*fan vertex*/) * sizeof(SkPoint); - } - - // Writes the vertex buffer to use when rendering with a fixed count shader. - static void WriteFixedVertexBuffer(VertexWriter, size_t bufferSize); - - // Size of the index buffer to use when rendering with a fixed count shader. - constexpr static int FixedIndexBufferSize(int maxFixedResolveLevel) { - return (NumCurveTrianglesAtResolveLevel(maxFixedResolveLevel) + 1/*fan triangle*/) * - 3 * sizeof(uint16_t); - } - - // Writes the index buffer to use when rendering with a fixed count shader. - static void WriteFixedIndexBuffer(VertexWriter vertexWriter, size_t bufferSize); - -#if SK_GPU_V1 - void prepare(GrMeshDrawTarget* target, - int maxTessellationSegments, - const SkMatrix& shaderMatrix, - const PathDrawList& pathDrawList, - int totalCombinedPathVerbCnt, - bool willUseTessellationShaders) final; - - void prepareFixedCountBuffers(GrMeshDrawTarget*) final; - - void drawTessellated(GrOpFlushState*) const final; - void drawFixedCount(GrOpFlushState*) const final; -#endif -}; - -} // namespace skgpu - -#endif // tessellate_PathWedgeTessellator_DEFINED diff --git a/src/gpu/tessellate/StrokeFixedCountTessellator.cpp b/src/gpu/tessellate/StrokeFixedCountTessellator.cpp deleted file mode 100644 index 6930195292..0000000000 --- a/src/gpu/tessellate/StrokeFixedCountTessellator.cpp +++ /dev/null @@ -1,272 +0,0 @@ -/* - * 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/StrokeFixedCountTessellator.h" - -#include "src/core/SkGeometry.h" -#include "src/gpu/tessellate/PatchWriter.h" -#include "src/gpu/tessellate/StrokeIterator.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 { - -namespace { - -using Writer = PatchWriter, - Optional, - Optional, - Optional, - Optional, - TrackJoinControlPoints>; - -// Returns the worst-case number of edges we will need in order to draw a join of the given type. -int worst_case_edges_in_join(SkPaint::Join joinType, float numRadialSegmentsPerRadian) { - int numEdges = StrokeFixedCountTessellator::NumFixedEdgesInJoin(joinType); - if (joinType == SkPaint::kRound_Join) { - // For round joins we need to count the radial edges on our own. Account for a worst-case - // join of 180 degrees (SK_ScalarPI radians). - numEdges += std::max(SkScalarCeilToInt(numRadialSegmentsPerRadian * SK_ScalarPI) - 1, 0); - } - return numEdges; -} - -int write_patches(Writer&& patchWriter, - const SkMatrix& shaderMatrix, - std::array matrixMinMaxScales, - StrokeTessellator::PathStrokeList* pathStrokeList) { - int maxEdgesInJoin = 0; - float maxRadialSegmentsPerRadian = 0; - const float matrixMaxScale = matrixMinMaxScales[1]; - if (!(patchWriter.attribs() & PatchAttribs::kStrokeParams)) { - // Strokes are static. Calculate tolerances once. - const SkStrokeRec& stroke = pathStrokeList->fStroke; - float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(matrixMinMaxScales.data(), - stroke.getWidth()); - float numRadialSegmentsPerRadian = StrokeTolerances::CalcNumRadialSegmentsPerRadian( - matrixMaxScale, localStrokeWidth); - maxEdgesInJoin = worst_case_edges_in_join(stroke.getJoin(), numRadialSegmentsPerRadian); - maxRadialSegmentsPerRadian = numRadialSegmentsPerRadian; - } - - // Fast SIMD queue that buffers up values for "numRadialSegmentsPerRadian". Only used when we - // have dynamic stroke. - StrokeToleranceBuffer toleranceBuffer(matrixMaxScale); - - // The vector xform approximates how the control points are transformed by the shader to - // more accurately compute how many *parametric* segments are needed. - wangs_formula::VectorXform shaderXform{shaderMatrix}; - for (auto* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) { - const SkStrokeRec& stroke = pathStroke->fStroke; - if (patchWriter.attribs() & PatchAttribs::kStrokeParams) { - // Strokes are dynamic. Calculate tolerances every time. - float numRadialSegmentsPerRadian = - toleranceBuffer.fetchRadialSegmentsPerRadian(pathStroke); - maxEdgesInJoin = std::max( - worst_case_edges_in_join(stroke.getJoin(), numRadialSegmentsPerRadian), - maxEdgesInJoin); - maxRadialSegmentsPerRadian = std::max(numRadialSegmentsPerRadian, - maxRadialSegmentsPerRadian); - patchWriter.updateStrokeParamsAttrib(stroke); - } - if (patchWriter.attribs() & PatchAttribs::kColor) { - patchWriter.updateColorAttrib(pathStroke->fColor); - } - - StrokeIterator strokeIter(pathStroke->fPath, &pathStroke->fStroke, &shaderMatrix); - while (strokeIter.next()) { - using Verb = StrokeIterator::Verb; - const SkPoint* p = strokeIter.pts(); - int numChops; - switch (strokeIter.verb()) { - case Verb::kContourFinished: - patchWriter.writeDeferredStrokePatch(); - break; - case Verb::kCircle: - // Round cap or else an empty stroke that is specified to be drawn as a circle. - patchWriter.writeCircle(p[0]); - [[fallthrough]]; - case Verb::kMoveWithinContour: - // A regular kMove invalidates the previous control point; the stroke iterator - // tells us a new value to use. - patchWriter.updateJoinControlPointAttrib(p[0]); - break; - case Verb::kLine: - patchWriter.writeLine(p[0], p[1]); - break; - case Verb::kQuad: - if (ConicHasCusp(p)) { - // The cusp is always at the midtandent. - SkPoint cusp = SkEvalQuadAt(p, SkFindQuadMidTangent(p)); - patchWriter.writeCircle(cusp); - // A quad can only have a cusp if it's flat with a 180-degree turnaround. - patchWriter.writeLine(p[0], cusp); - patchWriter.writeLine(cusp, p[2]); - } else { - patchWriter.writeQuadratic(p, shaderXform); - } - break; - case Verb::kConic: - if (ConicHasCusp(p)) { - // The cusp is always at the midtandent. - SkConic conic(p, strokeIter.w()); - SkPoint cusp = conic.evalAt(conic.findMidTangent()); - patchWriter.writeCircle(cusp); - // A conic can only have a cusp if it's flat with a 180-degree turnaround. - patchWriter.writeLine(p[0], cusp); - patchWriter.writeLine(cusp, p[2]); - } else { - patchWriter.writeConic(p, strokeIter.w(), shaderXform); - } - break; - case Verb::kCubic: - SkPoint chops[10]; - float T[2]; - bool areCusps; - numChops = FindCubicConvex180Chops(p, T, &areCusps); - if (numChops == 0) { - patchWriter.writeCubic(p, shaderXform); - } else if (numChops == 1) { - SkChopCubicAt(p, chops, T[0]); - if (areCusps) { - patchWriter.writeCircle(chops[3]); - // In a perfect world, these 3 points would be be equal after chopping - // on a cusp. - chops[2] = chops[4] = chops[3]; - } - patchWriter.writeCubic(chops, shaderXform); - patchWriter.writeCubic(chops + 3, shaderXform); - } else { - SkASSERT(numChops == 2); - SkChopCubicAt(p, chops, T[0], T[1]); - if (areCusps) { - patchWriter.writeCircle(chops[3]); - patchWriter.writeCircle(chops[6]); - // Two cusps are only possible if it's a flat line with two 180-degree - // turnarounds. - patchWriter.writeLine(chops[0], chops[3]); - patchWriter.writeLine(chops[3], chops[6]); - patchWriter.writeLine(chops[6], chops[9]); - } else { - patchWriter.writeCubic(chops, shaderXform); - patchWriter.writeCubic(chops + 3, shaderXform); - patchWriter.writeCubic(chops + 6, shaderXform); - } - } - break; - } - } - } - - // The maximum rotation we can have in a stroke is 180 degrees (SK_ScalarPI radians). - int maxRadialSegmentsInStroke = - std::max(SkScalarCeilToInt(maxRadialSegmentsPerRadian * SK_ScalarPI), 1); - - int maxParametricSegmentsInStroke = patchWriter.requiredFixedSegments(); - SkASSERT(maxParametricSegmentsInStroke >= 1); - - // Now calculate the maximum number of edges we will need in the stroke portion of the instance. - // The first and last edges in a stroke are shared by both the parametric and radial sets of - // edges, so the total number of edges is: - // - // numCombinedEdges = numParametricEdges + numRadialEdges - 2 - // - // It's also important to differentiate between the number of edges and segments in a strip: - // - // numSegments = numEdges - 1 - // - // So the total number of combined edges in the stroke is: - // - // numEdgesInStroke = numParametricSegments + 1 + numRadialSegments + 1 - 2 - // = numParametricSegments + numRadialSegments - // - int maxEdgesInStroke = maxRadialSegmentsInStroke + maxParametricSegmentsInStroke; - - // Each triangle strip has two sections: It starts with a join then transitions to a stroke. The - // number of edges in an instance is the sum of edges from the join and stroke sections both. - // NOTE: The final join edge and the first stroke edge are co-located, however we still need to - // emit both because the join's edge is half-width and the stroke's is full-width. - return maxEdgesInJoin + maxEdgesInStroke; -} - -} // namespace - -void StrokeFixedCountTessellator::InitializeVertexIDFallbackBuffer(VertexWriter vertexWriter, - size_t bufferSize) { - SkASSERT(bufferSize % (sizeof(float) * 2) == 0); - int edgeCount = bufferSize / (sizeof(float) * 2); - for (int i = 0; i < edgeCount; ++i) { - vertexWriter << (float)i << (float)-i; - } -} - -#if SK_GPU_V1 - -SKGPU_DECLARE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey); - -int StrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target, - const SkMatrix& shaderMatrix, - std::array matrixMinMaxScales, - PathStrokeList* pathStrokeList, - int totalCombinedStrokeVerbCnt) { - Writer patchWriter{fAttribs, kMaxParametricSegments, - target, &fVertexChunkArray, PatchPreallocCount(totalCombinedStrokeVerbCnt)}; - - fFixedEdgeCount = write_patches(std::move(patchWriter), - shaderMatrix, - matrixMinMaxScales, - pathStrokeList); - - // Don't draw more vertices than can be indexed by a signed short. We just have to draw the line - // somewhere and this seems reasonable enough. (There are two vertices per edge, so 2^14 edges - // make 2^15 vertices.) - fFixedEdgeCount = std::min(fFixedEdgeCount, (1 << 14) - 1); - - if (!target->caps().shaderCaps()->vertexIDSupport()) { - // Our shader won't be able to use sk_VertexID. Bind a fallback vertex buffer with the IDs - // in it instead. - constexpr static int kMaxVerticesInFallbackBuffer = 2048; - fFixedEdgeCount = std::min(fFixedEdgeCount, kMaxVerticesInFallbackBuffer/2); - - SKGPU_DEFINE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey); - - fVertexBufferIfNoIDSupport = target->resourceProvider()->findOrMakeStaticBuffer( - GrGpuBufferType::kVertex, - kMaxVerticesInFallbackBuffer * sizeof(float), - gVertexIDFallbackBufferKey, - InitializeVertexIDFallbackBuffer); - } - - return fFixedEdgeCount; -} - -void StrokeFixedCountTessellator::draw(GrOpFlushState* flushState) const { - if (fVertexChunkArray.empty() || fFixedEdgeCount <= 0) { - return; - } - if (!flushState->caps().shaderCaps()->vertexIDSupport() && - !fVertexBufferIfNoIDSupport) { - return; - } - for (const auto& instanceChunk : fVertexChunkArray) { - flushState->bindBuffers(nullptr, instanceChunk.fBuffer, fVertexBufferIfNoIDSupport); - flushState->drawInstanced(instanceChunk.fCount, - instanceChunk.fBase, - fFixedEdgeCount * 2, - 0); - } -} - -#endif - -} // namespace skgpu diff --git a/src/gpu/tessellate/StrokeFixedCountTessellator.h b/src/gpu/tessellate/StrokeFixedCountTessellator.h deleted file mode 100644 index 997f493896..0000000000 --- a/src/gpu/tessellate/StrokeFixedCountTessellator.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2021 Google LLC. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#ifndef tessellate_StrokeFixedCountTessellator_DEFINED -#define tessellate_StrokeFixedCountTessellator_DEFINED - -#include "src/gpu/tessellate/StrokeTessellator.h" - -#if SK_GPU_V1 -#include "src/gpu/GrGpuBuffer.h" -#endif - -namespace skgpu { - -// Renders strokes as fixed-count triangle strip instances. Any extra triangles not needed by the -// instance are emitted as degenerate triangles. -class StrokeFixedCountTessellator final : public StrokeTessellator { -public: - constexpr static int kMaxParametricSegments = 32; - constexpr static int8_t kMaxParametricSegments_log2 = 5; // log2(32) - - StrokeFixedCountTessellator(PatchAttribs attribs) : StrokeTessellator(attribs) {} - - static int PatchPreallocCount(int totalCombinedStrokeVerbCnt) { - // Over-allocate enough patches for each stroke to chop once, and for 8 extra caps. Since we - // have to chop at inflections, points of 180 degree rotation, and anywhere a stroke - // requires too many parametric segments, many strokes will end up getting choppped. - int strokePreallocCount = totalCombinedStrokeVerbCnt * 2; - int capPreallocCount = 8; - return strokePreallocCount + capPreallocCount; - } - -#if SK_GPU_V1 - int prepare(GrMeshDrawTarget*, - const SkMatrix& shaderMatrix, - std::array matrixMinMaxScales, - PathStrokeList*, - int totalCombinedStrokeVerbCnt) final; - - void draw(GrOpFlushState*) const final; -#endif - - // Initializes the fallback vertex buffer that should be bound when sk_VertexID is not - // supported. Each vertex is a single float and each edge is composed of two vertices, so the - // desired edge count in the buffer is presumed to be "bufferSize / (sizeof(float) * 2)". The - // caller cannot draw more vertices than edgeCount * 2. - static void InitializeVertexIDFallbackBuffer(VertexWriter vertexWriter, size_t bufferSize); - - // Returns the fixed number of edges that are always emitted with the given join type. If the - // join is round, the caller needs to account for the additional radial edges on their own. - // Specifically, each join always emits: - // - // * Two colocated edges at the beginning (a full-width edge to seam with the preceding stroke - // and a half-width edge to begin the join). - // - // * An extra edge in the middle for miter joins, or else a variable number of radial edges - // for round joins (the caller is responsible for counting radial edges from round joins). - // - // * A half-width edge at the end of the join that will be colocated with the first - // (full-width) edge of the stroke. - // - constexpr static int NumFixedEdgesInJoin(SkPaint::Join joinType) { - switch (joinType) { - case SkPaint::kMiter_Join: - return 4; - case SkPaint::kRound_Join: - // The caller is responsible for counting the variable number of middle, radial - // segments on round joins. - [[fallthrough]]; - case SkPaint::kBevel_Join: - return 3; - } - SkUNREACHABLE; - } - -private: -#if SK_GPU_V1 - int fFixedEdgeCount = 0; - - // Only used if sk_VertexID is not supported. - sk_sp fVertexBufferIfNoIDSupport; -#endif -}; - -} // namespace skgpu - -#endif // tessellate_StrokeFixedCountTessellator_DEFINED diff --git a/src/gpu/tessellate/StrokeHardwareTessellator.h b/src/gpu/tessellate/StrokeHardwareTessellator.h deleted file mode 100644 index 78118c0127..0000000000 --- a/src/gpu/tessellate/StrokeHardwareTessellator.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2020 Google LLC. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#ifndef tessellate_StrokeHardwareTessellator_DEFINED -#define tessellate_StrokeHardwareTessellator_DEFINED - -#include "src/gpu/tessellate/StrokeTessellator.h" - -namespace skgpu { - -// Renders opaque, constant-color strokes by decomposing them into standalone tessellation patches. -// Each patch is either a "cubic" (single stroked bezier curve with butt caps) or a "join". Requires -// MSAA if antialiasing is desired. -class StrokeHardwareTessellator final : public StrokeTessellator { -public: - StrokeHardwareTessellator(PatchAttribs attribs, int maxTessellationSegments) - : StrokeTessellator(attribs), fMaxTessellationSegments(maxTessellationSegments) {} - -#if SK_GPU_V1 - int prepare(GrMeshDrawTarget*, - const SkMatrix& shaderMatrix, - std::array matrixMinMaxScales, - PathStrokeList*, - int totalCombinedStrokeVerbCnt) final; - - void draw(GrOpFlushState*) const final; -#endif - -private: - const int fMaxTessellationSegments; -}; - -} // namespace skgpu - -#endif // tessellate_StrokeHardwareTessellator_DEFINED diff --git a/src/gpu/tessellate/StrokeTessellator.h b/src/gpu/tessellate/StrokeTessellator.h deleted file mode 100644 index eca39374e6..0000000000 --- a/src/gpu/tessellate/StrokeTessellator.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2021 Google LLC. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#ifndef tessellate_StrokeTessellator_DEFINED -#define tessellate_StrokeTessellator_DEFINED - -#include "include/core/SkPath.h" -#include "include/core/SkStrokeRec.h" -#include "include/private/SkColorData.h" -#include "src/gpu/tessellate/Tessellation.h" - -#if SK_GPU_V1 -#include "src/gpu/GrVertexChunkArray.h" - -class GrMeshDrawTarget; -class GrOpFlushState; -#endif - -namespace skgpu { - -// Prepares GPU data for, and then draws a stroke's tessellated geometry. -class StrokeTessellator { -public: - struct PathStrokeList { - PathStrokeList(const SkPath& path, const SkStrokeRec& stroke, const SkPMColor4f& color) - : fPath(path), fStroke(stroke), fColor(color) {} - SkPath fPath; - SkStrokeRec fStroke; - SkPMColor4f fColor; - PathStrokeList* fNext = nullptr; - }; - - StrokeTessellator(PatchAttribs attribs) : fAttribs(attribs | PatchAttribs::kJoinControlPoint) {} - -#if SK_GPU_V1 - // Called before draw(). Prepares GPU buffers containing the geometry to tessellate. - // - // Returns the fixed number of edges the tessellator will draw per patch, if using fixed-count - // rendering, otherwise 0. - virtual int prepare(GrMeshDrawTarget*, - const SkMatrix& shaderMatrix, - std::array matrixMinMaxScales, - PathStrokeList*, - int totalCombinedStrokeVerbCnt) = 0; - - // Issues draw calls for the tessellated stroke. The caller is responsible for creating and - // binding a pipeline that uses this class's shader() before calling draw(). - virtual void draw(GrOpFlushState*) const = 0; -#endif - - virtual ~StrokeTessellator() {} - -protected: - const PatchAttribs fAttribs; - -#if SK_GPU_V1 - GrVertexChunkArray fVertexChunkArray; -#endif -}; - -// These tolerances decide the number of parametric and radial segments the tessellator will -// linearize strokes into. These decisions are made in (pre-viewMatrix) local path space. -class StrokeTolerances { - StrokeTolerances() = delete; -public: - // Decides the number of radial segments the tessellator adds for each curve. (Uniform steps - // in tangent angle.) The tessellator will add this number of radial segments for each - // radian of rotation in local path space. - static float CalcNumRadialSegmentsPerRadian(float matrixMaxScale, float strokeWidth) { - float cosTheta = 1.f - (1.f / kTessellationPrecision) / (matrixMaxScale * strokeWidth); - return .5f / acosf(std::max(cosTheta, -1.f)); - } - template - static vec ApproxNumRadialSegmentsPerRadian(float matrixMaxScale, vec strokeWidths) { - vec cosTheta = 1.f - (1.f / kTessellationPrecision) / (matrixMaxScale * strokeWidths); - // Subtract SKVX_APPROX_ACOS_MAX_ERROR so we never account for too few segments. - return .5f / (approx_acos(max(cosTheta, -1.f)) - SKVX_APPROX_ACOS_MAX_ERROR); - } - // Returns the equivalent stroke width in (pre-viewMatrix) local path space that the - // tessellator will use when rendering this stroke. This only differs from the actual stroke - // width for hairlines. - static float GetLocalStrokeWidth(const float matrixMinMaxScales[2], float strokeWidth) { - SkASSERT(strokeWidth >= 0); - float localStrokeWidth = strokeWidth; - if (localStrokeWidth == 0) { // Is the stroke a hairline? - float matrixMinScale = matrixMinMaxScales[0]; - float matrixMaxScale = matrixMinMaxScales[1]; - // If the stroke is hairline then the tessellator will operate in post-transform - // space instead. But for the sake of CPU methods that need to conservatively - // approximate the number of segments to emit, we use - // localStrokeWidth ~= 1/matrixMinScale. - float approxScale = matrixMinScale; - // If the matrix has strong skew, don't let the scale shoot off to infinity. (This - // does not affect the tessellator; only the CPU methods that approximate the number - // of segments to emit.) - approxScale = std::max(matrixMinScale, matrixMaxScale * .25f); - localStrokeWidth = 1/approxScale; - if (localStrokeWidth == 0) { - // We just can't accidentally return zero from this method because zero means - // "hairline". Otherwise return whatever we calculated above. - localStrokeWidth = SK_ScalarNearlyZero; - } - } - return localStrokeWidth; - } -}; - -// Calculates and buffers up future values for "numRadialSegmentsPerRadian" using SIMD. -class alignas(sizeof(float) * 4) StrokeToleranceBuffer { -public: - using PathStrokeList = StrokeTessellator::PathStrokeList; - - StrokeToleranceBuffer(float matrixMaxScale) : fMatrixMaxScale(matrixMaxScale) {} - - float fetchRadialSegmentsPerRadian(PathStrokeList* head) { - // StrokeTessellateOp::onCombineIfPossible does not allow hairlines to become dynamic. If - // this changes, we will need to call StrokeTolerances::GetLocalStrokeWidth() for each - // stroke. - SkASSERT(!head->fStroke.isHairlineStyle()); - if (fBufferIdx == 4) { - // We ran out of values. Peek ahead and buffer up 4 more. - PathStrokeList* peekAhead = head; - int i = 0; - do { - fStrokeWidths[i++] = peekAhead->fStroke.getWidth(); - } while ((peekAhead = peekAhead->fNext) && i < 4); - auto tol = StrokeTolerances::ApproxNumRadialSegmentsPerRadian(fMatrixMaxScale, - fStrokeWidths); - tol.store(fNumRadialSegmentsPerRadian); - fBufferIdx = 0; - } - SkASSERT(0 <= fBufferIdx && fBufferIdx < 4); - SkASSERT(fStrokeWidths[fBufferIdx] == head->fStroke.getWidth()); - return fNumRadialSegmentsPerRadian[fBufferIdx++]; - } - -private: - float4 fStrokeWidths{}; // Must be first for alignment purposes. - float fNumRadialSegmentsPerRadian[4]; - const float fMatrixMaxScale; - int fBufferIdx = 4; // Initialize the buffer as "empty"; -}; - -} // namespace skgpu - -#endif // tessellate_StrokeTessellator_DEFINED diff --git a/src/gpu/tessellate/Tessellation.cpp b/src/gpu/tessellate/Tessellation.cpp index 18abf6e29f..2a6d5ce9dc 100644 --- a/src/gpu/tessellate/Tessellation.cpp +++ b/src/gpu/tessellate/Tessellation.cpp @@ -327,4 +327,5 @@ int FindCubicConvex180Chops(const SkPoint pts[], float T[2], bool* areCusps) { } return 0; } + } // namespace skgpu diff --git a/src/gpu/tessellate/Tessellation.h b/src/gpu/tessellate/Tessellation.h index 18b22ca27c..26735d5930 100644 --- a/src/gpu/tessellate/Tessellation.h +++ b/src/gpu/tessellate/Tessellation.h @@ -85,39 +85,6 @@ enum class PatchAttribs { GR_MAKE_BITFIELD_CLASS_OPS(PatchAttribs) -// We encode all of a join's information in a single float value: -// -// Negative => Round Join -// Zero => Bevel Join -// Positive => Miter join, and the value is also the miter limit -// -static float GetJoinType(const SkStrokeRec& stroke) { - switch (stroke.getJoin()) { - case SkPaint::kRound_Join: return -1; - case SkPaint::kBevel_Join: return 0; - case SkPaint::kMiter_Join: SkASSERT(stroke.getMiter() >= 0); return stroke.getMiter(); - } - SkUNREACHABLE; -} - -// This float2 gets written out with each patch/instance if PatchAttribs::kStrokeParams is enabled. -struct StrokeParams { - StrokeParams() = default; - StrokeParams(const SkStrokeRec& stroke) { - this->set(stroke); - } - void set(const SkStrokeRec& stroke) { - fRadius = stroke.getWidth() * .5f; - fJoinType = GetJoinType(stroke); - } - static bool StrokesHaveEqualParams(const SkStrokeRec& a, const SkStrokeRec& b) { - return a.getWidth() == b.getWidth() && a.getJoin() == b.getJoin() && - (a.getJoin() != SkPaint::kMiter_Join || a.getMiter() == b.getMiter()); - } - float fRadius; - float fJoinType; // See GetJoinType(). -}; - // When PatchAttribs::kExplicitCurveType is set, these are the values that tell the GPU what type of // curve is being drawn. constexpr static float kCubicCurveType SK_MAYBE_UNUSED = 0; @@ -174,6 +141,86 @@ inline bool ConicHasCusp(const SkPoint p[3]) { return a.cross(b) == 0 && a.dot(b) < 0; } +// We encode all of a join's information in a single float value: +// +// Negative => Round Join +// Zero => Bevel Join +// Positive => Miter join, and the value is also the miter limit +// +static float GetJoinType(const SkStrokeRec& stroke) { + switch (stroke.getJoin()) { + case SkPaint::kRound_Join: return -1; + case SkPaint::kBevel_Join: return 0; + case SkPaint::kMiter_Join: SkASSERT(stroke.getMiter() >= 0); return stroke.getMiter(); + } + SkUNREACHABLE; +} + +// This float2 gets written out with each patch/instance if PatchAttribs::kStrokeParams is enabled. +struct StrokeParams { + StrokeParams() = default; + StrokeParams(const SkStrokeRec& stroke) { + this->set(stroke); + } + void set(const SkStrokeRec& stroke) { + fRadius = stroke.getWidth() * .5f; + fJoinType = GetJoinType(stroke); + } + static bool StrokesHaveEqualParams(const SkStrokeRec& a, const SkStrokeRec& b) { + return a.getWidth() == b.getWidth() && a.getJoin() == b.getJoin() && + (a.getJoin() != SkPaint::kMiter_Join || a.getMiter() == b.getMiter()); + } + float fRadius; + float fJoinType; // See GetJoinType(). +}; + +// These tolerances decide the number of parametric and radial segments the tessellator will +// linearize strokes into. These decisions are made in (pre-viewMatrix) local path space. +class StrokeTolerances { + StrokeTolerances() = delete; +public: + // Decides the number of radial segments the tessellator adds for each curve. (Uniform steps + // in tangent angle.) The tessellator will add this number of radial segments for each + // radian of rotation in local path space. + static float CalcNumRadialSegmentsPerRadian(float matrixMaxScale, float strokeWidth) { + float cosTheta = 1.f - (1.f / kTessellationPrecision) / (matrixMaxScale * strokeWidth); + return .5f / acosf(std::max(cosTheta, -1.f)); + } + template + static vec ApproxNumRadialSegmentsPerRadian(float matrixMaxScale, vec strokeWidths) { + vec cosTheta = 1.f - (1.f / kTessellationPrecision) / (matrixMaxScale * strokeWidths); + // Subtract SKVX_APPROX_ACOS_MAX_ERROR so we never account for too few segments. + return .5f / (approx_acos(max(cosTheta, -1.f)) - SKVX_APPROX_ACOS_MAX_ERROR); + } + // Returns the equivalent stroke width in (pre-viewMatrix) local path space that the + // tessellator will use when rendering this stroke. This only differs from the actual stroke + // width for hairlines. + static float GetLocalStrokeWidth(const float matrixMinMaxScales[2], float strokeWidth) { + SkASSERT(strokeWidth >= 0); + float localStrokeWidth = strokeWidth; + if (localStrokeWidth == 0) { // Is the stroke a hairline? + float matrixMinScale = matrixMinMaxScales[0]; + float matrixMaxScale = matrixMinMaxScales[1]; + // If the stroke is hairline then the tessellator will operate in post-transform + // space instead. But for the sake of CPU methods that need to conservatively + // approximate the number of segments to emit, we use + // localStrokeWidth ~= 1/matrixMinScale. + float approxScale = matrixMinScale; + // If the matrix has strong skew, don't let the scale shoot off to infinity. (This + // does not affect the tessellator; only the CPU methods that approximate the number + // of segments to emit.) + approxScale = std::max(matrixMinScale, matrixMaxScale * .25f); + localStrokeWidth = 1/approxScale; + if (localStrokeWidth == 0) { + // We just can't accidentally return zero from this method because zero means + // "hairline". Otherwise return whatever we calculated above. + localStrokeWidth = SK_ScalarNearlyZero; + } + } + return localStrokeWidth; + } +}; + } // namespace skgpu #endif // tessellate_Tessellation_DEFINED diff --git a/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp b/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp index 003e716e4f..ee974fbe62 100644 --- a/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp +++ b/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp @@ -10,7 +10,7 @@ #include "src/core/SkMathPriv.h" #include "src/gpu/KeyBuilder.h" #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" -#include "src/gpu/tessellate/PathTessellator.h" +#include "src/gpu/tessellate/FixedCountBufferUtils.h" #include "src/gpu/tessellate/Tessellation.h" #include "src/gpu/tessellate/WangsFormula.h" @@ -69,7 +69,7 @@ public: } int maxTessellationSegments(const GrShaderCaps&) const override { - return 1 << skgpu::PathTessellator::kMaxFixedResolveLevel; + return skgpu::kMaxParametricSegments; } private: @@ -98,9 +98,9 @@ std::unique_ptr MiddleOutShader::makeProgramIm const MiddleOutShader& middleOutShader = shader.cast(); v->defineConstant("PRECISION", skgpu::kTessellationPrecision); v->defineConstant("MAX_FIXED_RESOLVE_LEVEL", - (float)skgpu::PathTessellator::kMaxFixedResolveLevel); + (float)skgpu::kMaxFixedResolveLevel); v->defineConstant("MAX_FIXED_SEGMENTS", - (float)(1 << skgpu::PathTessellator::kMaxFixedResolveLevel)); + (float)(skgpu::kMaxParametricSegments)); v->insertFunction(skgpu::wangs_formula::as_sksl().c_str()); if (middleOutShader.fAttribs & PatchAttribs::kExplicitCurveType) { v->insertFunction(SkStringPrintf(R"( diff --git a/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp b/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp index da3cbd1702..7cbd31a512 100644 --- a/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp +++ b/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp @@ -11,7 +11,6 @@ #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" #include "src/gpu/glsl/GrGLSLVarying.h" #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" -#include "src/gpu/tessellate/StrokeTessellator.h" GrStrokeTessellationShader::GrStrokeTessellationShader(const GrShaderCaps& shaderCaps, Mode mode, diff --git a/src/gpu/tessellate/shaders/GrStrokeTessellationShader_InstancedImpl.cpp b/src/gpu/tessellate/shaders/GrStrokeTessellationShader_InstancedImpl.cpp index 742e44c2f0..5ac5ff64e1 100644 --- a/src/gpu/tessellate/shaders/GrStrokeTessellationShader_InstancedImpl.cpp +++ b/src/gpu/tessellate/shaders/GrStrokeTessellationShader_InstancedImpl.cpp @@ -9,7 +9,7 @@ #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" -#include "src/gpu/tessellate/StrokeFixedCountTessellator.h" +#include "src/gpu/tessellate/FixedCountBufferUtils.h" #include "src/gpu/tessellate/WangsFormula.h" using skgpu::VertexWriter; @@ -186,7 +186,7 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA } else { args.fVertBuilder->codeAppendf(R"( float numEdgesInJoin = %i;)", - skgpu::StrokeFixedCountTessellator::NumFixedEdgesInJoin(joinType)); + skgpu::NumFixedEdgesInJoin(joinType)); } args.fVertBuilder->codeAppend(R"(