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 <michaelludwig@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Michael Ludwig 2022-05-04 10:39:47 -04:00 committed by SkCQ
parent d7ddbe668d
commit 9a393dd52b
6 changed files with 92 additions and 99 deletions

View File

@ -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<const GrBuffer> buffer;
int baseVertex;

View File

@ -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)) {

View File

@ -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();

View File

@ -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(

View File

@ -8,86 +8,11 @@
#ifndef tessellate_FixedCountBufferUtils_DEFINED
#define tessellate_FixedCountBufferUtils_DEFINED
#include "include/core/SkPaint.h"
#include <algorithm>
#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() {

View File

@ -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<int N> using vec = skvx::Vec<N, float>;
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 {