From 9a393dd52b8defae663496d581566f0db0f24941 Mon Sep 17 00:00:00 2001 From: Michael Ludwig Date: Wed, 4 May 2022 10:39:47 -0400 Subject: [PATCH] Move fixed-count constants into Tessellation.h With HW tessellation removed, fixed-count is the only version of this approach, so the fixed-count-only constants can be part of Tessellation.h, leaving FixedCountBufferUtils.h purely for creating the vertex/index buffers. Bug: skia:13056, skia:13263 Change-Id: I413a9ebe19fe4427e1ebacee7484d57377abf44e Reviewed-on: https://skia-review.googlesource.com/c/skia/+/534563 Commit-Queue: Michael Ludwig Reviewed-by: Robert Phillips --- bench/TessellateBench.cpp | 2 +- src/gpu/ganesh/ops/PathStencilCoverOp.cpp | 7 +- .../render/MiddleOutFanRenderStep.cpp | 3 +- src/gpu/tessellate/BUILD.bazel | 3 +- src/gpu/tessellate/FixedCountBufferUtils.h | 79 +-------------- src/gpu/tessellate/Tessellation.h | 97 ++++++++++++++++--- 6 files changed, 92 insertions(+), 99 deletions(-) diff --git a/bench/TessellateBench.cpp b/bench/TessellateBench.cpp index 59f173b942..271cc7a51a 100644 --- a/bench/TessellateBench.cpp +++ b/bench/TessellateBench.cpp @@ -224,7 +224,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 * (MaxCombinedFanEdgesInPaths(kNumCubicsInChalkboard) - 2); + const int maxVerts = 3 * (kNumCubicsInChalkboard - 2); sk_sp buffer; int baseVertex; diff --git a/src/gpu/ganesh/ops/PathStencilCoverOp.cpp b/src/gpu/ganesh/ops/PathStencilCoverOp.cpp index 30ef4c013f..1da65fab78 100644 --- a/src/gpu/ganesh/ops/PathStencilCoverOp.cpp +++ b/src/gpu/ganesh/ops/PathStencilCoverOp.cpp @@ -235,10 +235,13 @@ 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 = MaxCombinedFanEdgesInPaths(fTotalCombinedPathVerbCnt); + // 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. // 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); + int maxTrianglesInFans = std::max(fTotalCombinedPathVerbCnt - 2, 0); int fanTriangleCount = 0; if (VertexWriter triangleVertexWriter = vertexAlloc.lockWriter(sizeof(SkPoint), maxTrianglesInFans * 3)) { diff --git a/src/gpu/graphite/render/MiddleOutFanRenderStep.cpp b/src/gpu/graphite/render/MiddleOutFanRenderStep.cpp index a65c2767e1..92039de288 100644 --- a/src/gpu/graphite/render/MiddleOutFanRenderStep.cpp +++ b/src/gpu/graphite/render/MiddleOutFanRenderStep.cpp @@ -39,8 +39,7 @@ void MiddleOutFanRenderStep::writeVertices(DrawWriter* writer, const DrawGeometr // paths to SkPath just to iterate their pts/verbs SkPath path = geom.shape().asPath(); - const int maxCombinedFanEdges = MaxCombinedFanEdgesInPaths(path.countVerbs()); - const int maxTrianglesInFans = std::max(maxCombinedFanEdges - 2, 0); + const int maxTrianglesInFans = std::max(path.countVerbs() - 2, 0); float depth = geom.order().depthAsFloat(); diff --git a/src/gpu/tessellate/BUILD.bazel b/src/gpu/tessellate/BUILD.bazel index 51cd3517a4..8e7d84c15f 100644 --- a/src/gpu/tessellate/BUILD.bazel +++ b/src/gpu/tessellate/BUILD.bazel @@ -66,6 +66,7 @@ generated_cc_atom( hdrs = ["Tessellation.h"], visibility = ["//:__subpackages__"], deps = [ + "//include/core:SkPaint_hdr", "//include/core:SkPoint_hdr", "//include/core:SkStrokeRec_hdr", "//include/gpu:GrTypes_hdr", @@ -117,7 +118,7 @@ generated_cc_atom( name = "FixedCountBufferUtils_hdr", hdrs = ["FixedCountBufferUtils.h"], visibility = ["//:__subpackages__"], - deps = ["//include/core:SkPaint_hdr"], + deps = [":Tessellation_hdr"], ) generated_cc_atom( diff --git a/src/gpu/tessellate/FixedCountBufferUtils.h b/src/gpu/tessellate/FixedCountBufferUtils.h index 0308a734db..ddfe7f6622 100644 --- a/src/gpu/tessellate/FixedCountBufferUtils.h +++ b/src/gpu/tessellate/FixedCountBufferUtils.h @@ -8,86 +8,11 @@ #ifndef tessellate_FixedCountBufferUtils_DEFINED #define tessellate_FixedCountBufferUtils_DEFINED -#include "include/core/SkPaint.h" - -#include - +#include "src/gpu/tessellate/Tessellation.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 @@ -140,7 +65,7 @@ public: 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; + return (totalCombinedPathVerbCnt * 5 + 3) / 4; } static constexpr size_t VertexBufferSize() { diff --git a/src/gpu/tessellate/Tessellation.h b/src/gpu/tessellate/Tessellation.h index 01515bb9d9..bcfd773e3b 100644 --- a/src/gpu/tessellate/Tessellation.h +++ b/src/gpu/tessellate/Tessellation.h @@ -8,6 +8,7 @@ #ifndef skgpu_tessellate_Tessellation_DEFINED #define skgpu_tessellate_Tessellation_DEFINED +#include "include/core/SkPaint.h" #include "include/core/SkPoint.h" #include "include/core/SkStrokeRec.h" #include "include/gpu/GrTypes.h" @@ -19,8 +20,6 @@ struct SkRect; namespace skgpu { -struct VertexWriter; - // Use familiar type names from SkSL. template using vec = skvx::Vec; using float2 = vec<2>; @@ -66,7 +65,46 @@ AI constexpr float pow4(float x) { return pow2(x*x); } #undef AI // Don't allow linearized segments to be off by more than 1/4th of a pixel from the true curve. -SK_MAYBE_UNUSED constexpr static float kTessellationPrecision = 4; +constexpr static float kTessellationPrecision = 4; + +// This is the maximum number of subdivisions of a Bezier curve that can be represented in the fixed +// count vertex and index buffers. If rendering a curve that requires more subdivisions, 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; + +// Don't tessellate paths that might have an individual curve that requires more than 1024 segments. +// (See wangs_formula::worst_case_cubic). If this is the case, call "PreChopPathCurves" first. +// Standard chopping, when Wang's formula is between kMaxParametricSegments and +// kMaxTessellationSegmentsPerCurve is handled automatically by PatchWriter. It differs from +// PreChopPathCurves in that it does no culling of offscreen chopped paths. +constexpr static float kMaxTessellationSegmentsPerCurve = 1024; + +// Returns a new path, equivalent to 'path' within the given viewport, whose verbs can all be drawn +// with 'maxSegments' tessellation segments or fewer, while staying within '1/tessellationPrecision' +// pixels of the true curve. Curves and chops that fall completely outside the viewport are +// flattened into lines. +SkPath PreChopPathCurves(float tessellationPrecision, + const SkPath&, + const SkMatrix&, + const SkRect& viewport); + +// 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; +} // Optional attribs that are included in tessellation patches, following the control points and in // the same order as they appear here. @@ -108,19 +146,6 @@ constexpr size_t PatchStride(PatchAttribs attribs) { return 4*sizeof(SkPoint) + PatchAttribsStride(attribs); } -// Don't tessellate paths that might have an individual curve that requires more than 1024 segments. -// (See wangs_formula::worst_case_cubic). If this is the case, call "PreChopPathCurves" first. -constexpr static float kMaxTessellationSegmentsPerCurve SK_MAYBE_UNUSED = 1024; - -// Returns a new path, equivalent to 'path' within the given viewport, whose verbs can all be drawn -// with 'maxSegments' tessellation segments or fewer, while staying within '1/tessellationPrecision' -// pixels of the true curve. Curves and chops that fall completely outside the viewport are -// flattened into lines. -SkPath PreChopPathCurves(float tessellationPrecision, - const SkPath&, - const SkMatrix&, - const SkRect& viewport); - // Finds 0, 1, or 2 T values at which to chop the given curve in order to guarantee the resulting // cubics are convex and rotate no more than 180 degrees. // @@ -176,6 +201,46 @@ struct StrokeParams { float fJoinType; // See GetJoinType(). }; + +// 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; +} + // 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 {