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 <robertphillips@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Michael Ludwig 2022-03-22 11:24:35 -04:00 committed by SkCQ
parent 8e3cacfe6a
commit 0378307904
35 changed files with 1365 additions and 1500 deletions

View File

@ -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 <vector>
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<const GrBuffer> buffer;
int baseVertex;
@ -411,4 +408,4 @@ DEF_BENCH(return new TessPrepareBench(
"GrStrokeFixedCountTessellator_motionmark");
)
} // namespace skgpu
} // namespace skgpu::v1

View File

@ -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};

View File

@ -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<DrawWriterAllocator,
AddTrianglesWhenChopping,
DiscardFlatCurves>;
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

View File

@ -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<DrawWriterAllocator,
Required<PatchAttribs::kFanPoint>>;
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

View File

@ -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",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<GrVertexChunkBuilder,
Optional<PatchAttribs::kColor>,
Optional<PatchAttribs::kWideColorIfEnabled>,
Optional<PatchAttribs::kExplicitCurveType>,
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<GrVertexChunkBuilder,
Required<PatchAttribs::kFanPoint>,
Optional<PatchAttribs::kColor>,
Optional<PatchAttribs::kWideColorIfEnabled>,
Optional<PatchAttribs::kExplicitCurveType>>;
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<const GrGpuBuffer> vertexBufferIfNeeded) const {
for (const GrVertexChunk& chunk : fVertexChunkArray) {
flushState->bindBuffers(nullptr, chunk.fBuffer, vertexBufferIfNeeded);
flushState->drawInstanced(chunk.fCount, chunk.fBase, 4, 0);
}
}
SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedCountWedgesVertexBufferKey);
SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedCountWedgesIndexBufferKey);
void PathWedgeTessellator::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

View File

@ -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<const SkMatrix&, const SkPath&, const SkPMColor4f&> 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<const GrGpuBuffer> fFixedVertexBuffer;
sk_sp<const GrGpuBuffer> 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<PathCurveTessellator>(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<const GrGpuBuffer> 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<PathWedgeTessellator>(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

View File

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

View File

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

View File

@ -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<GrVertexChunkBuilder,
Required<PatchAttribs::kJoinControlPoint>,
Optional<PatchAttribs::kStrokeParams>,
Optional<PatchAttribs::kColor>,
Optional<PatchAttribs::kWideColorIfEnabled>,
Optional<PatchAttribs::kExplicitCurveType>,
TrackJoinControlPoints>;
int write_fixed_count_patches(FixedCountStrokeWriter&& patchWriter,
const SkMatrix& shaderMatrix,
std::array<float,2> 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<float2>(p[0]);
float2 p1 = skvx::bit_pun<float2>(p[1]);
float2 p2 = skvx::bit_pun<float2>(p[2]);
float2 p3 = skvx::bit_pun<float2>(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<GrVertexChunkBuilder,
Required<PatchAttribs::kJoinControlPoint>,
Optional<PatchAttribs::kStrokeParams>,
@ -654,49 +890,10 @@ private:
SkPoint fJoinControlPoint;
};
SK_ALWAYS_INLINE bool cubic_has_cusp(const SkPoint p[4]) {
float2 p0 = skvx::bit_pun<float2>(p[0]);
float2 p1 = skvx::bit_pun<float2>(p[1]);
float2 p2 = skvx::bit_pun<float2>(p[2]);
float2 p3 = skvx::bit_pun<float2>(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<float,2> matrixMinMaxScales,
StrokeTessellator::PathStrokeList* pathStrokeList) {
void write_hw_patches(HwPatchWriter&& hwPatchWriter,
const SkMatrix& shaderMatrix,
std::array<float,2> 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<float,2> 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<float,2> 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

View File

@ -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<float,2> 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<float,2> matrixMinMaxScales,
PathStrokeList*,
int totalCombinedStrokeVerbCnt) final;
void draw(GrOpFlushState*) const final;
private:
int fFixedEdgeCount = 0;
// Only used if sk_VertexID is not supported.
sk_sp<const GrGpuBuffer> 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<float,2> matrixMinMaxScales,
PathStrokeList*,
int totalCombinedStrokeVerbCnt) final;
void draw(GrOpFlushState*) const final;
private:
const int fMaxTessellationSegments;
};
} // namespace skgpu::v1
#endif // StrokeTessellator_DEFINED

View File

@ -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 <array>
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<std::array<uint16_t, 3>> 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

View File

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

View File

@ -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<GrVertexChunkBuilder,
Optional<PatchAttribs::kColor>,
Optional<PatchAttribs::kWideColorIfEnabled>,
Optional<PatchAttribs::kExplicitCurveType>,
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<std::array<uint16_t, 3>> 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<const GrGpuBuffer> vertexBufferIfNeeded) const {
for (const GrVertexChunk& chunk : fVertexChunkArray) {
flushState->bindBuffers(nullptr, chunk.fBuffer, vertexBufferIfNeeded);
flushState->drawInstanced(chunk.fCount, chunk.fBase, 4, 0);
}
}
#endif
} // namespace skgpu

View File

@ -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<PathCurveTessellator>(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<const GrGpuBuffer> vertexBufferIfNeeded) const;
#endif
};
} // namespace skgpu
#endif // tessellate_PathCurveTessellator_DEFINED

View File

@ -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<const SkMatrix&, const SkPath&, const SkPMColor4f&> 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<const GrGpuBuffer> fFixedVertexBuffer;
sk_sp<const GrGpuBuffer> 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

View File

@ -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<GrVertexChunkBuilder,
Required<PatchAttribs::kFanPoint>,
Optional<PatchAttribs::kColor>,
Optional<PatchAttribs::kWideColorIfEnabled>,
Optional<PatchAttribs::kExplicitCurveType>>;
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

View File

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

View File

@ -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<GrVertexChunkBuilder,
Required<PatchAttribs::kJoinControlPoint>,
Optional<PatchAttribs::kStrokeParams>,
Optional<PatchAttribs::kColor>,
Optional<PatchAttribs::kWideColorIfEnabled>,
Optional<PatchAttribs::kExplicitCurveType>,
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<float,2> 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<float,2> 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

View File

@ -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<float,2> 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<const GrGpuBuffer> fVertexBufferIfNoIDSupport;
#endif
};
} // namespace skgpu
#endif // tessellate_StrokeFixedCountTessellator_DEFINED

View File

@ -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<float,2> matrixMinMaxScales,
PathStrokeList*,
int totalCombinedStrokeVerbCnt) final;
void draw(GrOpFlushState*) const final;
#endif
private:
const int fMaxTessellationSegments;
};
} // namespace skgpu
#endif // tessellate_StrokeHardwareTessellator_DEFINED

View File

@ -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<float,2> 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<int N>
static vec<N> ApproxNumRadialSegmentsPerRadian(float matrixMaxScale, vec<N> strokeWidths) {
vec<N> 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

View File

@ -327,4 +327,5 @@ int FindCubicConvex180Chops(const SkPoint pts[], float T[2], bool* areCusps) {
}
return 0;
}
} // namespace skgpu

View File

@ -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<int N>
static vec<N> ApproxNumRadialSegmentsPerRadian(float matrixMaxScale, vec<N> strokeWidths) {
vec<N> 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

View File

@ -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<GrGeometryProcessor::ProgramImpl> MiddleOutShader::makeProgramIm
const MiddleOutShader& middleOutShader = shader.cast<MiddleOutShader>();
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"(

View File

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

View File

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