From 037830790468ffc5004591788e3ab15760239dae Mon Sep 17 00:00:00 2001 From: Michael Ludwig Date: Tue, 22 Mar 2022 11:24:35 -0400 Subject: [PATCH] Move skgpu::v1-specific [Stroke|Path]Tessellators out of tessellate/ The static functionality for writing vertex buffers, constants, and utility functions that were in the StrokeTessellator and PathTessellator classes have been moved into Tessellation.h and a new FixedCountBufferUtils.h. The tessellator hierarchy has been moved into src/gpu/ops and all the v1 guards are removed since they were already solely v1 after the static functions were lifted out. The hierarchy and subclasses are preserved but have been combined into just StrokeTessellator.h and PathTessellator.h instead of separate files for the subclasses. All the rest of the little changes are updating references and includes. Bug: skia:13012 Change-Id: I90f2f53538349cf9ad1823a0c42fbdc772190a49 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/523189 Reviewed-by: Robert Phillips Commit-Queue: Michael Ludwig --- bench/TessellateBench.cpp | 17 +- .../src/render/MiddleOutFanRenderStep.cpp | 5 +- .../src/render/TessellateCurvesRenderStep.cpp | 29 +- .../src/render/TessellateWedgesRenderStep.cpp | 27 +- gn/gpu.gni | 20 +- samplecode/SamplePathTessellators.cpp | 7 +- src/gpu/ops/AtlasRenderTask.h | 2 +- src/gpu/ops/PathInnerTriangulateOp.cpp | 2 +- src/gpu/ops/PathInnerTriangulateOp.h | 6 +- src/gpu/ops/PathStencilCoverOp.cpp | 6 +- src/gpu/ops/PathStencilCoverOp.h | 2 +- src/gpu/ops/PathTessellateOp.cpp | 1 - src/gpu/ops/PathTessellateOp.h | 2 +- src/gpu/ops/PathTessellator.cpp | 303 ++++++++++++++ src/gpu/ops/PathTessellator.h | 191 +++++++++ src/gpu/ops/StrokeTessellateOp.cpp | 2 - src/gpu/ops/StrokeTessellateOp.h | 2 +- .../StrokeTessellator.cpp} | 370 +++++++++++++++--- src/gpu/ops/StrokeTessellator.h | 107 +++++ src/gpu/tessellate/FixedCountBufferUtils.cpp | 134 +++++++ src/gpu/tessellate/FixedCountBufferUtils.h | 187 +++++++++ src/gpu/tessellate/PathCurveTessellator.cpp | 254 ------------ src/gpu/tessellate/PathCurveTessellator.h | 98 ----- src/gpu/tessellate/PathTessellator.h | 144 ------- src/gpu/tessellate/PathWedgeTessellator.cpp | 194 --------- src/gpu/tessellate/PathWedgeTessellator.h | 74 ---- .../StrokeFixedCountTessellator.cpp | 272 ------------- .../tessellate/StrokeFixedCountTessellator.h | 91 ----- .../tessellate/StrokeHardwareTessellator.h | 39 -- src/gpu/tessellate/StrokeTessellator.h | 150 ------- src/gpu/tessellate/Tessellation.cpp | 1 + src/gpu/tessellate/Tessellation.h | 113 ++++-- .../GrPathTessellationShader_MiddleOut.cpp | 8 +- .../shaders/GrStrokeTessellationShader.cpp | 1 - ...StrokeTessellationShader_InstancedImpl.cpp | 4 +- 35 files changed, 1365 insertions(+), 1500 deletions(-) create mode 100644 src/gpu/ops/PathTessellator.cpp create mode 100644 src/gpu/ops/PathTessellator.h rename src/gpu/{tessellate/StrokeHardwareTessellator.cpp => ops/StrokeTessellator.cpp} (76%) create mode 100644 src/gpu/ops/StrokeTessellator.h create mode 100644 src/gpu/tessellate/FixedCountBufferUtils.cpp create mode 100644 src/gpu/tessellate/FixedCountBufferUtils.h delete mode 100644 src/gpu/tessellate/PathCurveTessellator.cpp delete mode 100644 src/gpu/tessellate/PathCurveTessellator.h delete mode 100644 src/gpu/tessellate/PathTessellator.h delete mode 100644 src/gpu/tessellate/PathWedgeTessellator.cpp delete mode 100644 src/gpu/tessellate/PathWedgeTessellator.h delete mode 100644 src/gpu/tessellate/StrokeFixedCountTessellator.cpp delete mode 100644 src/gpu/tessellate/StrokeFixedCountTessellator.h delete mode 100644 src/gpu/tessellate/StrokeHardwareTessellator.h delete mode 100644 src/gpu/tessellate/StrokeTessellator.h 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"(